]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/bpf-filter.cc
Merge pull request #11431 from jroessler-ox/docs-kskzskroll-update
[thirdparty/pdns.git] / pdns / bpf-filter.cc
CommitLineData
12471842
PL
1/*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
87b515ed 22#include "bpf-filter.hh"
becba800 23#include "iputils.hh"
dc22be50 24#include "dolog.hh"
87b515ed
RG
25
26#ifdef HAVE_EBPF
27
28#include <sys/syscall.h>
dc22be50 29#include <sys/resource.h>
87b515ed
RG
30#include <linux/bpf.h>
31
32#include "ext/libbpf/libbpf.h"
33
16f0c288
OM
34#include "misc.hh"
35
4a94ee50 36static __u64 ptr_to_u64(const void *ptr)
87b515ed
RG
37{
38 return (__u64) (unsigned long) ptr;
39}
40
38b10979 41/* these can be static as they are not declared in libbpf.h: */
4a94ee50 42static int bpf_pin_map(int descriptor, const std::string& path)
ad6bca0e
RG
43{
44 union bpf_attr attr;
45 memset(&attr, 0, sizeof(attr));
4a94ee50
RG
46 attr.bpf_fd = descriptor;
47 attr.pathname = ptr_to_u64(path.c_str());
ad6bca0e
RG
48 return syscall(SYS_bpf, BPF_OBJ_PIN, &attr, sizeof(attr));
49}
50
38b10979 51static int bpf_load_pinned_map(const std::string& path)
ad6bca0e
RG
52{
53 union bpf_attr attr;
54 memset(&attr, 0, sizeof(attr));
4a94ee50 55 attr.pathname = ptr_to_u64(path.c_str());
ad6bca0e
RG
56 return syscall(SYS_bpf, BPF_OBJ_GET, &attr, sizeof(attr));
57}
58
4a94ee50 59static void bpf_check_map_sizes(int descriptor, uint32_t expectedKeySize, uint32_t expectedValueSize)
ad6bca0e
RG
60{
61 struct bpf_map_info info;
62 uint32_t info_len = sizeof(info);
63 memset(&info, 0, sizeof(info));
64
65 union bpf_attr attr;
66 memset(&attr, 0, sizeof(attr));
4a94ee50 67 attr.info.bpf_fd = descriptor;
ad6bca0e
RG
68 attr.info.info_len = info_len;
69 attr.info.info = ptr_to_u64(&info);
70
71 int err = syscall(SYS_bpf, BPF_OBJ_GET_INFO_BY_FD, &attr, sizeof(attr));
72 if (err != 0) {
73 throw std::runtime_error("Error checking the size of eBPF map: " + stringerror());
74 }
75 if (info_len != sizeof(info)) {
76 throw std::runtime_error("Error checking the size of eBPF map: invalid info size returned");
77 }
78 if (info.key_size != expectedKeySize) {
79 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) + ")");
80 }
81 if (info.value_size != expectedValueSize) {
82 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) + ")");
83 }
ad6bca0e
RG
84}
85
94e3db7e 86// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
4a94ee50
RG
87static int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size,
88 int max_entries, int map_flags)
38b10979
RG
89{
90 union bpf_attr attr;
91 memset(&attr, 0, sizeof(attr));
92 attr.map_type = map_type;
93 attr.key_size = key_size;
94 attr.value_size = value_size;
95 attr.max_entries = max_entries;
becba800 96 attr.map_flags = map_flags;
38b10979
RG
97 return syscall(SYS_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
98}
99
4a94ee50 100static int bpf_update_elem(int descriptor, void *key, void *value, unsigned long long flags)
87b515ed 101{
eace2c24
RG
102 union bpf_attr attr;
103 memset(&attr, 0, sizeof(attr));
4a94ee50 104 attr.map_fd = descriptor;
87b515ed
RG
105 attr.key = ptr_to_u64(key);
106 attr.value = ptr_to_u64(value);
107 attr.flags = flags;
108 return syscall(SYS_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
109}
110
4a94ee50 111static int bpf_lookup_elem(int descriptor, void *key, void *value)
87b515ed 112{
eace2c24
RG
113 union bpf_attr attr;
114 memset(&attr, 0, sizeof(attr));
4a94ee50 115 attr.map_fd = descriptor;
87b515ed
RG
116 attr.key = ptr_to_u64(key);
117 attr.value = ptr_to_u64(value);
118 return syscall(SYS_bpf, BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
119}
120
4a94ee50 121static int bpf_delete_elem(int descriptor, void *key)
87b515ed 122{
eace2c24
RG
123 union bpf_attr attr;
124 memset(&attr, 0, sizeof(attr));
4a94ee50 125 attr.map_fd = descriptor;
87b515ed
RG
126 attr.key = ptr_to_u64(key);
127 return syscall(SYS_bpf, BPF_MAP_DELETE_ELEM, &attr, sizeof(attr));
128}
129
4a94ee50 130static int bpf_get_next_key(int descriptor, void *key, void *next_key)
87b515ed 131{
eace2c24
RG
132 union bpf_attr attr;
133 memset(&attr, 0, sizeof(attr));
4a94ee50 134 attr.map_fd = descriptor;
87b515ed
RG
135 attr.key = ptr_to_u64(key);
136 attr.next_key = ptr_to_u64(next_key);
137 return syscall(SYS_bpf, BPF_MAP_GET_NEXT_KEY, &attr, sizeof(attr));
138}
139
4a94ee50 140static int bpf_prog_load(enum bpf_prog_type prog_type,
94e3db7e 141 const struct bpf_insn *insns, size_t prog_len,
4a94ee50 142 const char *license, int kern_version)
87b515ed
RG
143{
144 char log_buf[65535];
eace2c24
RG
145 union bpf_attr attr;
146 memset(&attr, 0, sizeof(attr));
87b515ed
RG
147 attr.prog_type = prog_type;
148 attr.insns = ptr_to_u64((void *) insns);
94e3db7e 149 attr.insn_cnt = static_cast<int>(prog_len / sizeof(struct bpf_insn));
87b515ed
RG
150 attr.license = ptr_to_u64((void *) license);
151 attr.log_buf = ptr_to_u64(log_buf);
152 attr.log_size = sizeof(log_buf);
153 attr.log_level = 1;
154 /* assign one field outside of struct init to make sure any
155 * padding is zero initialized
156 */
157 attr.kern_version = kern_version;
158
159 long res = syscall(SYS_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
160 if (res == -1) {
161 if (errno == ENOSPC) {
162 /* not enough space in the log buffer */
163 attr.log_level = 0;
164 attr.log_size = 0;
165 attr.log_buf = ptr_to_u64(nullptr);
166 res = syscall(SYS_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
167 if (res != -1) {
168 return res;
169 }
170 }
a2a81d42 171 throw std::runtime_error("Error loading BPF program: (" + stringerror() + "):\n" + std::string(log_buf));
87b515ed
RG
172 }
173 return res;
174}
175
176struct KeyV6
177{
178 uint8_t src[16];
179};
180
181struct QNameKey
182{
183 uint8_t qname[255];
184};
185
053a020a 186struct QNameAndQTypeKey
87b515ed 187{
053a020a 188 uint8_t qname[255];
87b515ed
RG
189 uint16_t qtype;
190};
191
053a020a
RG
192struct QNameValue
193{
194 uint64_t counter{0};
195 uint16_t qtype{0};
196};
197
053a020a
RG
198
199BPFFilter::Map::Map(const BPFFilter::MapConfiguration& config, BPFFilter::MapFormat format): d_config(config)
87b515ed 200{
ad6bca0e
RG
201 if (d_config.d_type == BPFFilter::MapType::Filters) {
202 /* special case, this is a map of eBPF programs */
becba800 203 d_fd = FDWrapper(bpf_create_map(BPF_MAP_TYPE_PROG_ARRAY, sizeof(uint32_t), sizeof(uint32_t), d_config.d_maxItems, 0));
ad6bca0e
RG
204 if (d_fd.getHandle() == -1) {
205 throw std::runtime_error("Error creating a BPF program map of size " + std::to_string(d_config.d_maxItems) + ": " + stringerror());
206 }
87b515ed 207 }
ad6bca0e
RG
208 else {
209 int keySize = 0;
210 int valueSize = 0;
becba800
Y
211 int flags = 0;
212 bpf_map_type type = BPF_MAP_TYPE_HASH;
053a020a
RG
213 if (format == MapFormat::Legacy) {
214 switch (d_config.d_type) {
215 case MapType::IPv4:
216 keySize = sizeof(uint32_t);
217 valueSize = sizeof(uint64_t);
218 break;
219 case MapType::IPv6:
220 keySize = sizeof(KeyV6);
221 valueSize = sizeof(uint64_t);
222 break;
223 case MapType::QNames:
224 keySize = sizeof(QNameKey);
225 valueSize = sizeof(QNameValue);
226 break;
227 default:
2030c200 228 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?");
053a020a
RG
229 }
230 }
231 else {
232 switch (d_config.d_type) {
233 case MapType::IPv4:
234 keySize = sizeof(uint32_t);
235 valueSize = sizeof(CounterAndActionValue);
236 break;
237 case MapType::IPv6:
238 keySize = sizeof(KeyV6);
239 valueSize = sizeof(CounterAndActionValue);
240 break;
becba800
Y
241 case MapType::CIDR4:
242 keySize = sizeof(CIDR4);
243 valueSize = sizeof(CounterAndActionValue);
244 flags = BPF_F_NO_PREALLOC;
245 type = BPF_MAP_TYPE_LPM_TRIE;
246 break;
247 case MapType::CIDR6:
248 keySize = sizeof(CIDR6);
249 valueSize = sizeof(CounterAndActionValue);
250 flags = BPF_F_NO_PREALLOC;
251 type = BPF_MAP_TYPE_LPM_TRIE;
252 break;
053a020a
RG
253 case MapType::QNames:
254 keySize = sizeof(QNameAndQTypeKey);
255 valueSize = sizeof(CounterAndActionValue);
256 break;
257 default:
258 throw std::runtime_error("Unsupported eBPF map type: " + std::to_string(static_cast<uint8_t>(d_config.d_type)));
259 }
ad6bca0e 260 }
87b515ed 261
ad6bca0e
RG
262 if (!d_config.d_pinnedPath.empty()) {
263 /* try to load */
264 d_fd = FDWrapper(bpf_load_pinned_map(d_config.d_pinnedPath));
265 if (d_fd.getHandle() != -1) {
266 /* sanity checks: key and value size */
267 bpf_check_map_sizes(d_fd.getHandle(), keySize, valueSize);
becba800
Y
268 switch (d_config.d_type) {
269 case MapType::IPv4: {
ad6bca0e 270 uint32_t key = 0;
a918f0cf 271 while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
ad6bca0e 272 ++d_count;
ad6bca0e 273 }
7c06007f
Y
274 break;
275 }
becba800 276 case MapType::IPv6: {
ad6bca0e
RG
277 KeyV6 key;
278 memset(&key, 0, sizeof(key));
a918f0cf 279 while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
ad6bca0e 280 ++d_count;
ad6bca0e 281 }
7c06007f
Y
282 break;
283 }
becba800
Y
284 case MapType::CIDR4: {
285 CIDR4 key;
286 memset(&key, 0, sizeof(key));
287 while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
288 ++d_count;
289 }
7c06007f
Y
290 break;
291 }
becba800
Y
292 case MapType::CIDR6: {
293 CIDR6 key;
294 memset(&key, 0, sizeof(key));
295 while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
296 ++d_count;
297 }
7c06007f
Y
298 break;
299 }
becba800 300 case MapType::QNames: {
053a020a
RG
301 if (format == MapFormat::Legacy) {
302 QNameKey key;
303 memset(&key, 0, sizeof(key));
a918f0cf 304 while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
053a020a 305 ++d_count;
053a020a
RG
306 }
307 }
308 else {
309 QNameAndQTypeKey key;
310 memset(&key, 0, sizeof(key));
a918f0cf 311 while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
053a020a 312 ++d_count;
053a020a 313 }
ad6bca0e 314 }
7c06007f
Y
315 break;
316 }
becba800
Y
317
318 default:
319 throw std::runtime_error("Unsupported eBPF map type: " + std::to_string(static_cast<uint8_t>(d_config.d_type)));
ad6bca0e
RG
320 }
321 }
322 }
323
324 if (d_fd.getHandle() == -1) {
becba800 325 d_fd = FDWrapper(bpf_create_map(type, keySize, valueSize, static_cast<int>(d_config.d_maxItems), flags));
ad6bca0e
RG
326 if (d_fd.getHandle() == -1) {
327 throw std::runtime_error("Error creating a BPF map of size " + std::to_string(d_config.d_maxItems) + ": " + stringerror());
328 }
87b515ed 329
ad6bca0e
RG
330 if (!d_config.d_pinnedPath.empty()) {
331 if (bpf_pin_map(d_fd.getHandle(), d_config.d_pinnedPath) != 0) {
332 throw std::runtime_error("Unable to pin map to path '" + d_config.d_pinnedPath + "': " + stringerror());
333 }
334 }
335 }
87b515ed 336 }
ad6bca0e 337}
87b515ed 338
ad6bca0e
RG
339static FDWrapper loadProgram(const struct bpf_insn* filter, size_t filterSize)
340{
4a94ee50
RG
341 auto descriptor = FDWrapper(bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER,
342 filter,
343 filterSize,
344 "GPL",
345 0));
346 if (descriptor.getHandle() == -1) {
ad6bca0e 347 throw std::runtime_error("error loading BPF filter: " + stringerror());
87b515ed 348 }
4a94ee50 349 return descriptor;
ad6bca0e
RG
350}
351
352
411dccdb
Y
353BPFFilter::BPFFilter(std::unordered_map<std::string, MapConfiguration>& configs, BPFFilter::MapFormat format, bool external) :
354 d_mapFormat(format), d_external(external)
ad6bca0e 355{
053a020a
RG
356 if (d_mapFormat != BPFFilter::MapFormat::Legacy && !d_external) {
357 throw std::runtime_error("Unsupported eBPF map format, the current internal implemenation only supports the legacy format");
358 }
359
dc22be50 360 struct rlimit old_limit;
dc22be50
YS
361 if (getrlimit(RLIMIT_MEMLOCK, &old_limit) != 0) {
362 throw std::runtime_error("Unable to get memory lock limit: " + stringerror());
363 }
364
ad979582
OM
365 const rlim_t new_limit_size = 1024 * 1024;
366
7b6e1bb8
OM
367 /* Check if the current soft memlock limit is at least the limit */
368 if (old_limit.rlim_cur < new_limit_size) {
ad979582
OM
369 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);
370
dc22be50 371 struct rlimit new_limit;
ad979582
OM
372 new_limit.rlim_cur = new_limit_size;
373 new_limit.rlim_max = new_limit_size;
374
dc22be50 375 if (setrlimit(RLIMIT_MEMLOCK, &new_limit) != 0) {
ad979582 376 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());
dc22be50 377 }
dc22be50
YS
378 }
379
ad6bca0e 380 auto maps = d_maps.lock();
87b515ed 381
fc8ce347
Y
382 maps->d_v4 = BPFFilter::Map(configs["ipv4"], d_mapFormat);
383 maps->d_v6 = BPFFilter::Map(configs["ipv6"], d_mapFormat);
384 maps->d_qnames = BPFFilter::Map(configs["qnames"], d_mapFormat);
2030c200
Y
385
386 if (d_mapFormat != BPFFilter::MapFormat::Legacy) {
387 maps->d_cidr4 = BPFFilter::Map(configs["cidr4"], d_mapFormat);
388 maps->d_cidr6 = BPFFilter::Map(configs["cidr6"], d_mapFormat);
389 }
390
053a020a
RG
391 if (!external) {
392 BPFFilter::MapConfiguration filters;
393 filters.d_maxItems = 1;
394 filters.d_type = BPFFilter::MapType::Filters;
395 maps->d_filters = BPFFilter::Map(filters, d_mapFormat);
ad6bca0e 396
053a020a 397 const struct bpf_insn main_filter[] = {
d45189b7 398#include "bpf-filter.main.ebpf"
053a020a 399 };
d45189b7 400
053a020a 401 const struct bpf_insn qname_filter[] = {
d45189b7 402#include "bpf-filter.qname.ebpf"
053a020a 403 };
d45189b7 404
053a020a
RG
405 try {
406 d_mainfilter = loadProgram(main_filter,
407 sizeof(main_filter));
408 }
409 catch (const std::exception& e) {
410 throw std::runtime_error("Error load the main eBPF filter: " + std::string(e.what()));
411 }
ad6bca0e 412
053a020a
RG
413 try {
414 d_qnamefilter = loadProgram(qname_filter,
415 sizeof(qname_filter));
416 }
417 catch (const std::exception& e) {
418 throw std::runtime_error("Error load the qname eBPF filter: " + std::string(e.what()));
419 }
d45189b7 420
053a020a
RG
421 uint32_t key = 0;
422 int qnamefd = d_qnamefilter.getHandle();
423 int res = bpf_update_elem(maps->d_filters.d_fd.getHandle(), &key, &qnamefd, BPF_ANY);
424 if (res != 0) {
425 throw std::runtime_error("Error updating BPF filters map: " + stringerror());
426 }
87b515ed
RG
427 }
428}
429
430void BPFFilter::addSocket(int sock)
431{
4a94ee50
RG
432 int descriptor = d_mainfilter.getHandle();
433 int res = setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &descriptor, sizeof(descriptor));
87b515ed
RG
434
435 if (res != 0) {
a2a81d42 436 throw std::runtime_error("Error attaching BPF filter to this socket: " + stringerror());
87b515ed
RG
437 }
438}
439
8429ad04
RG
440void BPFFilter::removeSocket(int sock)
441{
4a94ee50
RG
442 int descriptor = d_mainfilter.getHandle();
443 int res = setsockopt(sock, SOL_SOCKET, SO_DETACH_BPF, &descriptor, sizeof(descriptor));
8429ad04
RG
444
445 if (res != 0) {
a2a81d42 446 throw std::runtime_error("Error detaching BPF filter from this socket: " + stringerror());
8429ad04
RG
447 }
448}
449
053a020a 450void BPFFilter::block(const ComboAddress& addr, BPFFilter::MatchAction action)
87b515ed 451{
053a020a
RG
452 CounterAndActionValue value;
453 value.counter = 0;
454 value.action = action;
455
87b515ed 456 int res = 0;
0da9ec8d 457 if (addr.isIPv4()) {
87b515ed 458 uint32_t key = htonl(addr.sin4.sin_addr.s_addr);
f0114d6b 459 auto maps = d_maps.lock();
ad6bca0e
RG
460 auto& map = maps->d_v4;
461 if (map.d_count >= map.d_config.d_maxItems) {
87b515ed
RG
462 throw std::runtime_error("Table full when trying to block " + addr.toString());
463 }
464
053a020a 465 res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
87b515ed
RG
466 if (res != -1) {
467 throw std::runtime_error("Trying to block an already blocked address: " + addr.toString());
468 }
469
053a020a 470 res = bpf_update_elem(map.d_fd.getHandle(), &key, &value, BPF_NOEXIST);
87b515ed 471 if (res == 0) {
ad6bca0e 472 ++map.d_count;
87b515ed
RG
473 }
474 }
0da9ec8d 475 else if (addr.isIPv6()) {
87b515ed
RG
476 uint8_t key[16];
477 static_assert(sizeof(addr.sin6.sin6_addr.s6_addr) == sizeof(key), "POSIX mandates s6_addr to be an array of 16 uint8_t");
478 for (size_t idx = 0; idx < sizeof(key); idx++) {
479 key[idx] = addr.sin6.sin6_addr.s6_addr[idx];
480 }
481
f0114d6b 482 auto maps = d_maps.lock();
ad6bca0e
RG
483 auto& map = maps->d_v6;
484 if (map.d_count >= map.d_config.d_maxItems) {
87b515ed
RG
485 throw std::runtime_error("Table full when trying to block " + addr.toString());
486 }
487
053a020a 488 res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
87b515ed
RG
489 if (res != -1) {
490 throw std::runtime_error("Trying to block an already blocked address: " + addr.toString());
491 }
492
053a020a 493 res = bpf_update_elem(map.d_fd.getHandle(), key, &value, BPF_NOEXIST);
87b515ed 494 if (res == 0) {
ad6bca0e 495 map.d_count++;
87b515ed
RG
496 }
497 }
498
499 if (res != 0) {
a2a81d42 500 throw std::runtime_error("Error adding blocked address " + addr.toString() + ": " + stringerror());
87b515ed
RG
501 }
502}
503
504void BPFFilter::unblock(const ComboAddress& addr)
505{
87b515ed 506 int res = 0;
0da9ec8d 507 if (addr.isIPv4()) {
87b515ed 508 uint32_t key = htonl(addr.sin4.sin_addr.s_addr);
f0114d6b 509 auto maps = d_maps.lock();
ad6bca0e
RG
510 auto& map = maps->d_v4;
511 res = bpf_delete_elem(map.d_fd.getHandle(), &key);
87b515ed 512 if (res == 0) {
ad6bca0e 513 --map.d_count;
87b515ed
RG
514 }
515 }
0da9ec8d 516 else if (addr.isIPv6()) {
87b515ed
RG
517 uint8_t key[16];
518 static_assert(sizeof(addr.sin6.sin6_addr.s6_addr) == sizeof(key), "POSIX mandates s6_addr to be an array of 16 uint8_t");
519 for (size_t idx = 0; idx < sizeof(key); idx++) {
520 key[idx] = addr.sin6.sin6_addr.s6_addr[idx];
521 }
522
f0114d6b 523 auto maps = d_maps.lock();
ad6bca0e
RG
524 auto& map = maps->d_v6;
525 res = bpf_delete_elem(map.d_fd.getHandle(), key);
87b515ed 526 if (res == 0) {
ad6bca0e 527 --map.d_count;
87b515ed
RG
528 }
529 }
530
531 if (res != 0) {
a2a81d42 532 throw std::runtime_error("Error removing blocked address " + addr.toString() + ": " + stringerror());
87b515ed
RG
533 }
534}
535
b31a0ea8 536void BPFFilter::addRangeRule(const Netmask& addr, bool force, BPFFilter::MatchAction action)
becba800
Y
537{
538 CounterAndActionValue value;
539
540 int res = 0;
541 if (addr.isIPv4()) {
542 CIDR4 key(addr);
543 auto maps = d_maps.lock();
544 auto& map = maps->d_cidr4;
2030c200
Y
545 if (map.d_fd.getHandle() == -1) {
546 throw std::runtime_error("Trying to use an unsupported map type, likely adding a range to a legacy eBPF program");
547 }
becba800 548 if (map.d_count >= map.d_config.d_maxItems) {
b31a0ea8 549 throw std::runtime_error("Table full when trying to add this rule: " + addr.toString());
becba800
Y
550 }
551
552 res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
b31a0ea8
Y
553 if (((res != -1 && value.action == action) || (res == -1 && value.action == BPFFilter::MatchAction::Pass)) && !force) {
554 throw std::runtime_error("Trying to add a useless rule: " + addr.toString());
becba800
Y
555 }
556
557 value.counter = 0;
558 value.action = action;
559
b1419e5e 560 res = bpf_update_elem(map.d_fd.getHandle(), &key, &value, force ? BPF_ANY : BPF_NOEXIST);
becba800
Y
561 if (res == 0) {
562 ++map.d_count;
563 }
564 }
565 else if (addr.isIPv6()) {
566 CIDR6 key(addr);
567
568 auto maps = d_maps.lock();
569 auto& map = maps->d_cidr6;
2030c200
Y
570 if (map.d_fd.getHandle() == -1) {
571 throw std::runtime_error("Trying to use an unsupported map type, likely adding a range to a legacy eBPF program");
572 }
becba800 573 if (map.d_count >= map.d_config.d_maxItems) {
b31a0ea8 574 throw std::runtime_error("Table full when trying to add this rule: " + addr.toString());
becba800
Y
575 }
576
577 res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
b31a0ea8
Y
578 if (((res != -1 && value.action == action) || (res == -1 && value.action == BPFFilter::MatchAction::Pass)) && !force) {
579 throw std::runtime_error("Trying to add a useless rule: " + addr.toString());
becba800
Y
580 }
581
582 value.counter = 0;
583 value.action = action;
584
585 res = bpf_update_elem(map.d_fd.getHandle(), &key, &value, BPF_NOEXIST);
586 if (res == 0) {
587 map.d_count++;
588 }
589 }
590
591 if (res != 0) {
b31a0ea8 592 throw std::runtime_error("Error adding this rule: " + addr.toString() + ": " + stringerror());
becba800
Y
593 }
594}
595
b31a0ea8 596void BPFFilter::rmRangeRule(const Netmask& addr)
becba800
Y
597{
598 int res = 0;
599 CounterAndActionValue value;
600 value.counter = 0;
601 value.action = MatchAction::Pass;
602 if (addr.isIPv4()) {
603 CIDR4 key(addr);
604 auto maps = d_maps.lock();
605 auto& map = maps->d_cidr4;
2030c200
Y
606 if (map.d_fd.getHandle() == -1) {
607 throw std::runtime_error("Trying to use an unsupported map type, likely adding a range to a legacy eBPF program");
608 }
becba800
Y
609 res = bpf_delete_elem(map.d_fd.getHandle(), &key);
610 if (res == 0) {
611 --map.d_count;
612 }
613 else {
b31a0ea8 614 throw std::runtime_error("Cannot remove '" + addr.toString() + "': No such rule");
becba800
Y
615 }
616 }
617 else if (addr.isIPv6()) {
618 CIDR6 key(addr);
619
620 auto maps = d_maps.lock();
621 auto& map = maps->d_cidr6;
2030c200
Y
622 if (map.d_fd.getHandle() == -1) {
623 throw std::runtime_error("Trying to use an unsupported map type, likely adding a range to a legacy eBPF program");
624 }
becba800
Y
625 res = bpf_delete_elem(map.d_fd.getHandle(), &key);
626 if (res == 0) {
627 --map.d_count;
628 }
629 else {
b31a0ea8 630 throw std::runtime_error("Cannot remove '" + addr.toString() + "': No such rule");
becba800
Y
631 }
632 }
633
634 if (res != 0) {
b31a0ea8 635 throw std::runtime_error("Error removing this rule: " + addr.toString() + ": " + stringerror());
becba800
Y
636 }
637}
638
053a020a 639void BPFFilter::block(const DNSName& qname, BPFFilter::MatchAction action, uint16_t qtype)
87b515ed 640{
053a020a
RG
641 CounterAndActionValue cadvalue;
642 QNameValue qvalue;
643 void* value = nullptr;
644
645 if (d_external) {
053a020a
RG
646 cadvalue.counter = 0;
647 cadvalue.action = action;
648 value = &cadvalue;
649 }
650 else {
053a020a
RG
651 qvalue.counter = 0;
652 qvalue.qtype = qtype;
653 value = &qvalue;
654 }
655
656 QNameAndQTypeKey key;
87b515ed 657 memset(&key, 0, sizeof(key));
87b515ed 658
d45189b7 659 std::string keyStr = qname.toDNSStringLC();
87b515ed 660 if (keyStr.size() > sizeof(key.qname)) {
86f1af1c 661 throw std::runtime_error("Invalid QName to block " + qname.toLogString());
87b515ed
RG
662 }
663 memcpy(key.qname, keyStr.c_str(), keyStr.size());
053a020a 664 key.qtype = qtype;
87b515ed
RG
665
666 {
f0114d6b 667 auto maps = d_maps.lock();
ad6bca0e
RG
668 auto& map = maps->d_qnames;
669 if (map.d_count >= map.d_config.d_maxItems) {
86f1af1c 670 throw std::runtime_error("Table full when trying to block " + qname.toLogString());
87b515ed
RG
671 }
672
815aee7a 673 int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, value);
87b515ed 674 if (res != -1) {
86f1af1c 675 throw std::runtime_error("Trying to block an already blocked qname: " + qname.toLogString());
87b515ed 676 }
815aee7a 677 res = bpf_update_elem(map.d_fd.getHandle(), &key, value, BPF_NOEXIST);
87b515ed 678 if (res == 0) {
ad6bca0e 679 ++map.d_count;
87b515ed
RG
680 }
681
682 if (res != 0) {
16f0c288 683 throw std::runtime_error("Error adding blocked qname " + qname.toLogString() + ": " + stringerror());
87b515ed
RG
684 }
685 }
686}
687
688void BPFFilter::unblock(const DNSName& qname, uint16_t qtype)
689{
053a020a
RG
690 QNameAndQTypeKey key;
691 memset(&key, 0, sizeof(key));
d45189b7 692 std::string keyStr = qname.toDNSStringLC();
87b515ed
RG
693
694 if (keyStr.size() > sizeof(key.qname)) {
86f1af1c 695 throw std::runtime_error("Invalid QName to block " + qname.toLogString());
87b515ed
RG
696 }
697 memcpy(key.qname, keyStr.c_str(), keyStr.size());
053a020a 698 key.qtype = qtype;
87b515ed
RG
699
700 {
f0114d6b 701 auto maps = d_maps.lock();
ad6bca0e
RG
702 auto& map = maps->d_qnames;
703 int res = bpf_delete_elem(map.d_fd.getHandle(), &key);
87b515ed 704 if (res == 0) {
ad6bca0e 705 --map.d_count;
87b515ed
RG
706 }
707 else {
16f0c288 708 throw std::runtime_error("Error removing qname address " + qname.toLogString() + ": " + stringerror());
87b515ed
RG
709 }
710 }
711}
712
713std::vector<std::pair<ComboAddress, uint64_t> > BPFFilter::getAddrStats()
714{
715 std::vector<std::pair<ComboAddress, uint64_t> > result;
f0114d6b
RG
716 {
717 auto maps = d_maps.lock();
ad6bca0e 718 result.reserve(maps->d_v4.d_count + maps->d_v6.d_count);
f0114d6b 719 }
87b515ed 720
d26d9f52 721 sockaddr_in v4Addr{};
eace2c24 722 memset(&v4Addr, 0, sizeof(v4Addr));
87b515ed
RG
723 v4Addr.sin_family = AF_INET;
724
0da9ec8d 725 uint32_t v4Key = 0;
d26d9f52
RG
726 uint32_t nextV4Key{};
727 CounterAndActionValue value{};
87b515ed 728
d26d9f52
RG
729 std::array<uint8_t, 16> v6Key{};
730 std::array<uint8_t, 16> nextV6Key{};
731 sockaddr_in6 v6Addr{};
eace2c24 732 memset(&v6Addr, 0, sizeof(v6Addr));
87b515ed 733 v6Addr.sin6_family = AF_INET6;
eace2c24 734
d26d9f52 735 static_assert(sizeof(v6Addr.sin6_addr.s6_addr) == v6Key.size(), "POSIX mandates s6_addr to be an array of 16 uint8_t");
c3186616 736 memset(&v6Key, 0, sizeof(v6Key));
87b515ed 737
f0114d6b 738 auto maps = d_maps.lock();
0da9ec8d 739
ad6bca0e
RG
740 {
741 auto& map = maps->d_v4;
742 int res = bpf_get_next_key(map.d_fd.getHandle(), &v4Key, &nextV4Key);
743
744 while (res == 0) {
745 v4Key = nextV4Key;
746 if (bpf_lookup_elem(map.d_fd.getHandle(), &v4Key, &value) == 0) {
0da9ec8d 747 v4Addr.sin_addr.s_addr = ntohl(v4Key);
053a020a 748 result.emplace_back(ComboAddress(&v4Addr), value.counter);
ad6bca0e 749 }
0da9ec8d 750
ad6bca0e
RG
751 res = bpf_get_next_key(map.d_fd.getHandle(), &v4Key, &nextV4Key);
752 }
0da9ec8d
RG
753 }
754
ad6bca0e
RG
755 {
756 auto& map = maps->d_v6;
d26d9f52 757 int res = bpf_get_next_key(map.d_fd.getHandle(), v6Key.data(), nextV6Key.data());
87b515ed 758
ad6bca0e 759 while (res == 0) {
d26d9f52
RG
760 if (bpf_lookup_elem(map.d_fd.getHandle(), nextV6Key.data(), &value) == 0) {
761 memcpy(&v6Addr.sin6_addr.s6_addr, nextV6Key.data(), nextV6Key.size());
c3186616 762
053a020a 763 result.emplace_back(ComboAddress(&v6Addr), value.counter);
ad6bca0e 764 }
87b515ed 765
d26d9f52 766 res = bpf_get_next_key(map.d_fd.getHandle(), nextV6Key.data(), nextV6Key.data());
ad6bca0e 767 }
87b515ed 768 }
ad6bca0e 769
87b515ed
RG
770 return result;
771}
772
b31a0ea8 773std::vector<std::pair<Netmask, CounterAndActionValue>> BPFFilter::getRangeRule()
783b8632
Y
774{
775 CIDR4 cidr4[2];
776 CIDR6 cidr6[2];
b31a0ea8 777 std::vector<std::pair<Netmask, CounterAndActionValue>> result;
783b8632
Y
778
779 sockaddr_in v4Addr;
780 sockaddr_in6 v6Addr;
781 CounterAndActionValue value;
782
783 memset(cidr4, 0, sizeof(cidr4));
784 memset(cidr6, 0, sizeof(cidr6));
785 memset(&v4Addr, 0, sizeof(v4Addr));
786 memset(&v6Addr, 0, sizeof(v6Addr));
3930c0e7
Y
787 v4Addr.sin_family = AF_INET;
788 v6Addr.sin6_family = AF_INET6;
783b8632
Y
789 auto maps = d_maps.lock();
790 result.reserve(maps->d_cidr4.d_count + maps->d_cidr6.d_count);
791 {
792 auto& map = maps->d_cidr4;
793 int res = bpf_get_next_key(map.d_fd.getHandle(), &cidr4[0], &cidr4[1]);
794 while (res == 0) {
795 if (bpf_lookup_elem(map.d_fd.getHandle(), &cidr4[1], &value) == 0) {
796 v4Addr.sin_addr.s_addr = cidr4[1].addr.s_addr;
b31a0ea8 797 result.emplace_back(Netmask(&v4Addr, cidr4[1].cidr), value);
783b8632
Y
798 }
799
800 res = bpf_get_next_key(map.d_fd.getHandle(), &cidr4[1], &cidr4[1]);
801 }
802 }
803
804 {
805 auto& map = maps->d_cidr6;
806 int res = bpf_get_next_key(map.d_fd.getHandle(), &cidr6[0], &cidr6[1]);
807 while (res == 0) {
808 if (bpf_lookup_elem(map.d_fd.getHandle(), &cidr6[1], &value) == 0) {
11cf516b 809 v6Addr.sin6_addr = cidr6[1].addr;
b31a0ea8 810 result.emplace_back(Netmask(&v6Addr, cidr6[1].cidr), value);
783b8632
Y
811 }
812
813 res = bpf_get_next_key(map.d_fd.getHandle(), &cidr6[1], &cidr6[1]);
814 }
815 }
816 return result;
817}
818
87b515ed
RG
819std::vector<std::tuple<DNSName, uint16_t, uint64_t> > BPFFilter::getQNameStats()
820{
821 std::vector<std::tuple<DNSName, uint16_t, uint64_t> > result;
87b515ed 822
053a020a
RG
823 if (d_mapFormat == MapFormat::Legacy) {
824 QNameKey key = { { 0 } };
825 QNameKey nextKey = { { 0 } };
826 QNameValue value;
87b515ed 827
053a020a
RG
828 auto maps = d_maps.lock();
829 auto& map = maps->d_qnames;
830 result.reserve(map.d_count);
831 int res = bpf_get_next_key(map.d_fd.getHandle(), &key, &nextKey);
87b515ed 832
053a020a
RG
833 while (res == 0) {
834 if (bpf_lookup_elem(map.d_fd.getHandle(), &nextKey, &value) == 0) {
835 nextKey.qname[sizeof(nextKey.qname) - 1 ] = '\0';
0b0882f5 836 result.emplace_back(DNSName(reinterpret_cast<const char*>(nextKey.qname), sizeof(nextKey.qname), 0, false), value.qtype, value.counter);
053a020a
RG
837 }
838
839 res = bpf_get_next_key(map.d_fd.getHandle(), &nextKey, &nextKey);
87b515ed 840 }
053a020a
RG
841 }
842 else {
843 QNameAndQTypeKey key;
844 QNameAndQTypeKey nextKey;
845 memset(&key, 0, sizeof(key));
846 memset(&nextKey, 0, sizeof(nextKey));
847 CounterAndActionValue value;
848
849 auto maps = d_maps.lock();
850 auto& map = maps->d_qnames;
851 result.reserve(map.d_count);
852 int res = bpf_get_next_key(map.d_fd.getHandle(), &key, &nextKey);
87b515ed 853
053a020a
RG
854 while (res == 0) {
855 if (bpf_lookup_elem(map.d_fd.getHandle(), &nextKey, &value) == 0) {
856 nextKey.qname[sizeof(nextKey.qname) - 1 ] = '\0';
0b0882f5 857 result.emplace_back(DNSName(reinterpret_cast<const char*>(nextKey.qname), sizeof(nextKey.qname), 0, false), key.qtype, value.counter);
053a020a
RG
858 }
859
860 res = bpf_get_next_key(map.d_fd.getHandle(), &nextKey, &nextKey);
861 }
87b515ed 862 }
053a020a 863
87b515ed
RG
864 return result;
865}
0da9ec8d
RG
866
867uint64_t BPFFilter::getHits(const ComboAddress& requestor)
868{
053a020a
RG
869 CounterAndActionValue counter;
870
0da9ec8d
RG
871 if (requestor.isIPv4()) {
872 uint32_t key = htonl(requestor.sin4.sin_addr.s_addr);
873
f0114d6b 874 auto maps = d_maps.lock();
ad6bca0e
RG
875 auto& map = maps->d_v4;
876 int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter);
0da9ec8d 877 if (res == 0) {
053a020a 878 return counter.counter;
0da9ec8d
RG
879 }
880 }
881 else if (requestor.isIPv6()) {
882 uint8_t key[16];
883 static_assert(sizeof(requestor.sin6.sin6_addr.s6_addr) == sizeof(key), "POSIX mandates s6_addr to be an array of 16 uint8_t");
884 for (size_t idx = 0; idx < sizeof(key); idx++) {
885 key[idx] = requestor.sin6.sin6_addr.s6_addr[idx];
886 }
887
f0114d6b 888 auto maps = d_maps.lock();
ad6bca0e
RG
889 auto& map = maps->d_v6;
890 int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter);
0da9ec8d 891 if (res == 0) {
053a020a 892 return counter.counter;
0da9ec8d
RG
893 }
894 }
895
896 return 0;
897}
898
899#else
900
411dccdb 901BPFFilter::BPFFilter(std::unordered_map<std::string, MapConfiguration>& configs, BPFFilter::MapFormat format, bool external)
0da9ec8d
RG
902{
903}
904
ad6bca0e 905void BPFFilter::addSocket(int)
0da9ec8d 906{
0da9ec8d
RG
907 throw std::runtime_error("eBPF support not enabled");
908}
909
ad6bca0e 910void BPFFilter::removeSocket(int)
0da9ec8d 911{
0da9ec8d
RG
912 throw std::runtime_error("eBPF support not enabled");
913}
914
053a020a 915void BPFFilter::block(const ComboAddress&, BPFFilter::MatchAction)
0da9ec8d 916{
0da9ec8d
RG
917 throw std::runtime_error("eBPF support not enabled");
918}
919
ad6bca0e 920void BPFFilter::unblock(const ComboAddress&)
0da9ec8d 921{
0da9ec8d
RG
922 throw std::runtime_error("eBPF support not enabled");
923}
924
053a020a 925void BPFFilter::block(const DNSName&, BPFFilter::MatchAction, uint16_t)
0da9ec8d 926{
0da9ec8d
RG
927 throw std::runtime_error("eBPF support not enabled");
928}
929
ad6bca0e 930void BPFFilter::unblock(const DNSName&, uint16_t)
0da9ec8d 931{
0da9ec8d
RG
932 throw std::runtime_error("eBPF support not enabled");
933}
934
b31a0ea8 935void BPFFilter::addRangeRule(const Netmask&, bool, BPFFilter::MatchAction)
becba800
Y
936{
937 throw std::runtime_error("eBPF support not enabled");
938}
b31a0ea8 939void BPFFilter::rmRangeRule(const Netmask&)
becba800
Y
940{
941 throw std::runtime_error("eBPF support not enabled");
942}
943
b31a0ea8
Y
944std::vector<std::pair<Netmask, CounterAndActionValue>> BPFFilter::getRangeRule(){
945 std::vector<std::pair<Netmask, CounterAndActionValue>> result;
946 return result;
947}
0da9ec8d
RG
948std::vector<std::pair<ComboAddress, uint64_t> > BPFFilter::getAddrStats()
949{
950 std::vector<std::pair<ComboAddress, uint64_t> > result;
951 return result;
952}
953
954std::vector<std::tuple<DNSName, uint16_t, uint64_t> > BPFFilter::getQNameStats()
955{
956 std::vector<std::tuple<DNSName, uint16_t, uint64_t> > result;
957 return result;
958}
959
ad6bca0e 960uint64_t BPFFilter::getHits(const ComboAddress&)
0da9ec8d 961{
0da9ec8d
RG
962 return 0;
963}
87b515ed 964#endif /* HAVE_EBPF */
053a020a
RG
965
966bool BPFFilter::supportsMatchAction(MatchAction action) const
967{
968#ifdef HAVE_EBPF
969 if (action == BPFFilter::MatchAction::Drop) {
970 return true;
971 }
972 return d_mapFormat == BPFFilter::MapFormat::WithActions;
973#endif /* HAVE_EBPF */
974 return false;
975}
976
977bool BPFFilter::isExternal() const
978{
979#ifdef HAVE_EBPF
980 return d_external;
981#endif /* HAVE_EBPF */
982 return false;
983}