]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/bpf-filter.cc
Merge pull request #14200 from rgacogne/auth-enable-leak-detection-unit-tests
[thirdparty/pdns.git] / pdns / bpf-filter.cc
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 */
22 #include "bpf-filter.hh"
23 #include "iputils.hh"
24 #include "dolog.hh"
25
26 #ifdef HAVE_EBPF
27
28 #include <sys/syscall.h>
29 #include <sys/resource.h>
30 #include <linux/bpf.h>
31
32 #include "ext/libbpf/libbpf.h"
33
34 #include "misc.hh"
35
36 static __u64 ptr_to_u64(const void *ptr)
37 {
38 return (__u64) (unsigned long) ptr;
39 }
40
41 /* these can be static as they are not declared in libbpf.h: */
42 static int bpf_pin_map(int descriptor, const std::string& path)
43 {
44 union bpf_attr attr;
45 memset(&attr, 0, sizeof(attr));
46 attr.bpf_fd = descriptor;
47 attr.pathname = ptr_to_u64(path.c_str());
48 return syscall(SYS_bpf, BPF_OBJ_PIN, &attr, sizeof(attr));
49 }
50
51 static int bpf_load_pinned_map(const std::string& path)
52 {
53 union bpf_attr attr;
54 memset(&attr, 0, sizeof(attr));
55 attr.pathname = ptr_to_u64(path.c_str());
56 return syscall(SYS_bpf, BPF_OBJ_GET, &attr, sizeof(attr));
57 }
58
59 static void bpf_check_map_sizes(int descriptor, uint32_t expectedKeySize, uint32_t expectedValueSize)
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));
67 attr.info.bpf_fd = descriptor;
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 }
84 }
85
86 // NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
87 static int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size,
88 int max_entries, int map_flags)
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;
96 attr.map_flags = map_flags;
97 return syscall(SYS_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
98 }
99
100 static int bpf_update_elem(int descriptor, void *key, void *value, unsigned long long flags)
101 {
102 union bpf_attr attr;
103 memset(&attr, 0, sizeof(attr));
104 attr.map_fd = descriptor;
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
111 static int bpf_lookup_elem(int descriptor, void *key, void *value)
112 {
113 union bpf_attr attr;
114 memset(&attr, 0, sizeof(attr));
115 attr.map_fd = descriptor;
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
121 static int bpf_delete_elem(int descriptor, void *key)
122 {
123 union bpf_attr attr;
124 memset(&attr, 0, sizeof(attr));
125 attr.map_fd = descriptor;
126 attr.key = ptr_to_u64(key);
127 return syscall(SYS_bpf, BPF_MAP_DELETE_ELEM, &attr, sizeof(attr));
128 }
129
130 static int bpf_get_next_key(int descriptor, void *key, void *next_key)
131 {
132 union bpf_attr attr;
133 memset(&attr, 0, sizeof(attr));
134 attr.map_fd = descriptor;
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
140 static int bpf_prog_load(enum bpf_prog_type prog_type,
141 const struct bpf_insn *insns, size_t prog_len,
142 const char *license, int kern_version)
143 {
144 char log_buf[65535];
145 union bpf_attr attr;
146 memset(&attr, 0, sizeof(attr));
147 attr.prog_type = prog_type;
148 attr.insns = ptr_to_u64((void *) insns);
149 attr.insn_cnt = static_cast<int>(prog_len / sizeof(struct bpf_insn));
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 }
171 throw std::runtime_error("Error loading BPF program: (" + stringerror() + "):\n" + std::string(log_buf));
172 }
173 return res;
174 }
175
176 struct KeyV6
177 {
178 uint8_t src[16];
179 };
180
181 struct QNameKey
182 {
183 uint8_t qname[255];
184 };
185
186 struct QNameAndQTypeKey
187 {
188 uint8_t qname[255];
189 uint16_t qtype;
190 };
191
192 struct QNameValue
193 {
194 uint64_t counter{0};
195 uint16_t qtype{0};
196 };
197
198
199 BPFFilter::Map::Map(const BPFFilter::MapConfiguration& config, BPFFilter::MapFormat format): d_config(config)
200 {
201 if (d_config.d_type == BPFFilter::MapType::Filters) {
202 /* special case, this is a map of eBPF programs */
203 d_fd = FDWrapper(bpf_create_map(BPF_MAP_TYPE_PROG_ARRAY, sizeof(uint32_t), sizeof(uint32_t), d_config.d_maxItems, 0));
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 }
207 }
208 else {
209 int keySize = 0;
210 int valueSize = 0;
211 int flags = 0;
212 bpf_map_type type = BPF_MAP_TYPE_HASH;
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:
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?");
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;
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;
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 }
260 }
261
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);
268 switch (d_config.d_type) {
269 case MapType::IPv4: {
270 uint32_t key = 0;
271 while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
272 ++d_count;
273 }
274 break;
275 }
276 case MapType::IPv6: {
277 KeyV6 key;
278 memset(&key, 0, sizeof(key));
279 while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
280 ++d_count;
281 }
282 break;
283 }
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 }
290 break;
291 }
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 }
298 break;
299 }
300 case MapType::QNames: {
301 if (format == MapFormat::Legacy) {
302 QNameKey key;
303 memset(&key, 0, sizeof(key));
304 while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
305 ++d_count;
306 }
307 }
308 else {
309 QNameAndQTypeKey key;
310 memset(&key, 0, sizeof(key));
311 while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
312 ++d_count;
313 }
314 }
315 break;
316 }
317
318 default:
319 throw std::runtime_error("Unsupported eBPF map type: " + std::to_string(static_cast<uint8_t>(d_config.d_type)));
320 }
321 }
322 }
323
324 if (d_fd.getHandle() == -1) {
325 d_fd = FDWrapper(bpf_create_map(type, keySize, valueSize, static_cast<int>(d_config.d_maxItems), flags));
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 }
329
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 }
336 }
337 }
338
339 static FDWrapper loadProgram(const struct bpf_insn* filter, size_t filterSize)
340 {
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) {
347 throw std::runtime_error("error loading BPF filter: " + stringerror());
348 }
349 return descriptor;
350 }
351
352
353 BPFFilter::BPFFilter(std::unordered_map<std::string, MapConfiguration>& configs, BPFFilter::MapFormat format, bool external) :
354 d_mapFormat(format), d_external(external)
355 {
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
360 struct rlimit old_limit;
361 if (getrlimit(RLIMIT_MEMLOCK, &old_limit) != 0) {
362 throw std::runtime_error("Unable to get memory lock limit: " + stringerror());
363 }
364
365 const rlim_t new_limit_size = 1024 * 1024;
366
367 /* Check if the current soft memlock limit is at least the limit */
368 if (old_limit.rlim_cur < new_limit_size) {
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
371 struct rlimit new_limit;
372 new_limit.rlim_cur = new_limit_size;
373 new_limit.rlim_max = new_limit_size;
374
375 if (setrlimit(RLIMIT_MEMLOCK, &new_limit) != 0) {
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());
377 }
378 }
379
380 auto maps = d_maps.lock();
381
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);
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
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);
396
397 const struct bpf_insn main_filter[] = {
398 #include "bpf-filter.main.ebpf"
399 };
400
401 const struct bpf_insn qname_filter[] = {
402 #include "bpf-filter.qname.ebpf"
403 };
404
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 }
412
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 }
420
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 }
427 }
428 }
429
430 void BPFFilter::addSocket(int sock)
431 {
432 int descriptor = d_mainfilter.getHandle();
433 int res = setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &descriptor, sizeof(descriptor));
434
435 if (res != 0) {
436 throw std::runtime_error("Error attaching BPF filter to this socket: " + stringerror());
437 }
438 }
439
440 void BPFFilter::removeSocket(int sock)
441 {
442 int descriptor = d_mainfilter.getHandle();
443 int res = setsockopt(sock, SOL_SOCKET, SO_DETACH_BPF, &descriptor, sizeof(descriptor));
444
445 if (res != 0) {
446 throw std::runtime_error("Error detaching BPF filter from this socket: " + stringerror());
447 }
448 }
449
450 void BPFFilter::block(const ComboAddress& addr, BPFFilter::MatchAction action)
451 {
452 CounterAndActionValue value;
453 value.counter = 0;
454 value.action = action;
455
456 int res = 0;
457 if (addr.isIPv4()) {
458 uint32_t key = htonl(addr.sin4.sin_addr.s_addr);
459 auto maps = d_maps.lock();
460 auto& map = maps->d_v4;
461 if (map.d_count >= map.d_config.d_maxItems) {
462 throw std::runtime_error("Table full when trying to block " + addr.toString());
463 }
464
465 res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
466 if (res != -1) {
467 throw std::runtime_error("Trying to block an already blocked address: " + addr.toString());
468 }
469
470 res = bpf_update_elem(map.d_fd.getHandle(), &key, &value, BPF_NOEXIST);
471 if (res == 0) {
472 ++map.d_count;
473 }
474 }
475 else if (addr.isIPv6()) {
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
482 auto maps = d_maps.lock();
483 auto& map = maps->d_v6;
484 if (map.d_count >= map.d_config.d_maxItems) {
485 throw std::runtime_error("Table full when trying to block " + addr.toString());
486 }
487
488 res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
489 if (res != -1) {
490 throw std::runtime_error("Trying to block an already blocked address: " + addr.toString());
491 }
492
493 res = bpf_update_elem(map.d_fd.getHandle(), key, &value, BPF_NOEXIST);
494 if (res == 0) {
495 map.d_count++;
496 }
497 }
498
499 if (res != 0) {
500 throw std::runtime_error("Error adding blocked address " + addr.toString() + ": " + stringerror());
501 }
502 }
503
504 void BPFFilter::unblock(const ComboAddress& addr)
505 {
506 int res = 0;
507 if (addr.isIPv4()) {
508 uint32_t key = htonl(addr.sin4.sin_addr.s_addr);
509 auto maps = d_maps.lock();
510 auto& map = maps->d_v4;
511 res = bpf_delete_elem(map.d_fd.getHandle(), &key);
512 if (res == 0) {
513 --map.d_count;
514 }
515 }
516 else if (addr.isIPv6()) {
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
523 auto maps = d_maps.lock();
524 auto& map = maps->d_v6;
525 res = bpf_delete_elem(map.d_fd.getHandle(), key);
526 if (res == 0) {
527 --map.d_count;
528 }
529 }
530
531 if (res != 0) {
532 throw std::runtime_error("Error removing blocked address " + addr.toString() + ": " + stringerror());
533 }
534 }
535
536 void BPFFilter::addRangeRule(const Netmask& addr, bool force, BPFFilter::MatchAction action)
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;
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 }
548 if (map.d_count >= map.d_config.d_maxItems) {
549 throw std::runtime_error("Table full when trying to add this rule: " + addr.toString());
550 }
551
552 res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
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());
555 }
556
557 value.counter = 0;
558 value.action = action;
559
560 res = bpf_update_elem(map.d_fd.getHandle(), &key, &value, force ? BPF_ANY : BPF_NOEXIST);
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;
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 }
573 if (map.d_count >= map.d_config.d_maxItems) {
574 throw std::runtime_error("Table full when trying to add this rule: " + addr.toString());
575 }
576
577 res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
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());
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) {
592 throw std::runtime_error("Error adding this rule: " + addr.toString() + ": " + stringerror());
593 }
594 }
595
596 void BPFFilter::rmRangeRule(const Netmask& addr)
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;
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 }
609 res = bpf_delete_elem(map.d_fd.getHandle(), &key);
610 if (res == 0) {
611 --map.d_count;
612 }
613 else {
614 throw std::runtime_error("Cannot remove '" + addr.toString() + "': No such rule");
615 }
616 }
617 else if (addr.isIPv6()) {
618 CIDR6 key(addr);
619
620 auto maps = d_maps.lock();
621 auto& map = maps->d_cidr6;
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 }
625 res = bpf_delete_elem(map.d_fd.getHandle(), &key);
626 if (res == 0) {
627 --map.d_count;
628 }
629 else {
630 throw std::runtime_error("Cannot remove '" + addr.toString() + "': No such rule");
631 }
632 }
633
634 if (res != 0) {
635 throw std::runtime_error("Error removing this rule: " + addr.toString() + ": " + stringerror());
636 }
637 }
638
639 void BPFFilter::block(const DNSName& qname, BPFFilter::MatchAction action, uint16_t qtype)
640 {
641 CounterAndActionValue cadvalue;
642 QNameValue qvalue;
643 void* value = nullptr;
644
645 if (d_external) {
646 cadvalue.counter = 0;
647 cadvalue.action = action;
648 value = &cadvalue;
649 }
650 else {
651 qvalue.counter = 0;
652 qvalue.qtype = qtype;
653 value = &qvalue;
654 }
655
656 QNameAndQTypeKey key;
657 memset(&key, 0, sizeof(key));
658
659 std::string keyStr = qname.toDNSStringLC();
660 if (keyStr.size() > sizeof(key.qname)) {
661 throw std::runtime_error("Invalid QName to block " + qname.toLogString());
662 }
663 memcpy(key.qname, keyStr.c_str(), keyStr.size());
664 key.qtype = qtype;
665
666 {
667 auto maps = d_maps.lock();
668 auto& map = maps->d_qnames;
669 if (map.d_count >= map.d_config.d_maxItems) {
670 throw std::runtime_error("Table full when trying to block " + qname.toLogString());
671 }
672
673 int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, value);
674 if (res != -1) {
675 throw std::runtime_error("Trying to block an already blocked qname: " + qname.toLogString());
676 }
677 res = bpf_update_elem(map.d_fd.getHandle(), &key, value, BPF_NOEXIST);
678 if (res == 0) {
679 ++map.d_count;
680 }
681
682 if (res != 0) {
683 throw std::runtime_error("Error adding blocked qname " + qname.toLogString() + ": " + stringerror());
684 }
685 }
686 }
687
688 void BPFFilter::unblock(const DNSName& qname, uint16_t qtype)
689 {
690 QNameAndQTypeKey key;
691 memset(&key, 0, sizeof(key));
692 std::string keyStr = qname.toDNSStringLC();
693
694 if (keyStr.size() > sizeof(key.qname)) {
695 throw std::runtime_error("Invalid QName to block " + qname.toLogString());
696 }
697 memcpy(key.qname, keyStr.c_str(), keyStr.size());
698 key.qtype = qtype;
699
700 {
701 auto maps = d_maps.lock();
702 auto& map = maps->d_qnames;
703 int res = bpf_delete_elem(map.d_fd.getHandle(), &key);
704 if (res == 0) {
705 --map.d_count;
706 }
707 else {
708 throw std::runtime_error("Error removing qname address " + qname.toLogString() + ": " + stringerror());
709 }
710 }
711 }
712
713 std::vector<std::pair<ComboAddress, uint64_t> > BPFFilter::getAddrStats()
714 {
715 std::vector<std::pair<ComboAddress, uint64_t> > result;
716 {
717 auto maps = d_maps.lock();
718 result.reserve(maps->d_v4.d_count + maps->d_v6.d_count);
719 }
720
721 sockaddr_in v4Addr{};
722 memset(&v4Addr, 0, sizeof(v4Addr));
723 v4Addr.sin_family = AF_INET;
724
725 uint32_t v4Key = 0;
726 uint32_t nextV4Key{};
727 CounterAndActionValue value{};
728
729 std::array<uint8_t, 16> v6Key{};
730 std::array<uint8_t, 16> nextV6Key{};
731 sockaddr_in6 v6Addr{};
732 memset(&v6Addr, 0, sizeof(v6Addr));
733 v6Addr.sin6_family = AF_INET6;
734
735 static_assert(sizeof(v6Addr.sin6_addr.s6_addr) == v6Key.size(), "POSIX mandates s6_addr to be an array of 16 uint8_t");
736 memset(&v6Key, 0, sizeof(v6Key));
737
738 auto maps = d_maps.lock();
739
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) {
747 v4Addr.sin_addr.s_addr = ntohl(v4Key);
748 result.emplace_back(ComboAddress(&v4Addr), value.counter);
749 }
750
751 res = bpf_get_next_key(map.d_fd.getHandle(), &v4Key, &nextV4Key);
752 }
753 }
754
755 {
756 auto& map = maps->d_v6;
757 int res = bpf_get_next_key(map.d_fd.getHandle(), v6Key.data(), nextV6Key.data());
758
759 while (res == 0) {
760 if (bpf_lookup_elem(map.d_fd.getHandle(), nextV6Key.data(), &value) == 0) {
761 memcpy(&v6Addr.sin6_addr.s6_addr, nextV6Key.data(), nextV6Key.size());
762
763 result.emplace_back(ComboAddress(&v6Addr), value.counter);
764 }
765
766 res = bpf_get_next_key(map.d_fd.getHandle(), nextV6Key.data(), nextV6Key.data());
767 }
768 }
769
770 return result;
771 }
772
773 std::vector<std::pair<Netmask, CounterAndActionValue>> BPFFilter::getRangeRule()
774 {
775 CIDR4 cidr4[2];
776 CIDR6 cidr6[2];
777 std::vector<std::pair<Netmask, CounterAndActionValue>> result;
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));
787 v4Addr.sin_family = AF_INET;
788 v6Addr.sin6_family = AF_INET6;
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;
797 result.emplace_back(Netmask(&v4Addr, cidr4[1].cidr), value);
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) {
809 v6Addr.sin6_addr = cidr6[1].addr;
810 result.emplace_back(Netmask(&v6Addr, cidr6[1].cidr), value);
811 }
812
813 res = bpf_get_next_key(map.d_fd.getHandle(), &cidr6[1], &cidr6[1]);
814 }
815 }
816 return result;
817 }
818
819 std::vector<std::tuple<DNSName, uint16_t, uint64_t> > BPFFilter::getQNameStats()
820 {
821 std::vector<std::tuple<DNSName, uint16_t, uint64_t> > result;
822
823 if (d_mapFormat == MapFormat::Legacy) {
824 QNameKey key = { { 0 } };
825 QNameKey nextKey = { { 0 } };
826 QNameValue value;
827
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);
832
833 while (res == 0) {
834 if (bpf_lookup_elem(map.d_fd.getHandle(), &nextKey, &value) == 0) {
835 nextKey.qname[sizeof(nextKey.qname) - 1 ] = '\0';
836 result.emplace_back(DNSName(reinterpret_cast<const char*>(nextKey.qname), sizeof(nextKey.qname), 0, false), value.qtype, value.counter);
837 }
838
839 res = bpf_get_next_key(map.d_fd.getHandle(), &nextKey, &nextKey);
840 }
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);
853
854 while (res == 0) {
855 if (bpf_lookup_elem(map.d_fd.getHandle(), &nextKey, &value) == 0) {
856 nextKey.qname[sizeof(nextKey.qname) - 1 ] = '\0';
857 result.emplace_back(DNSName(reinterpret_cast<const char*>(nextKey.qname), sizeof(nextKey.qname), 0, false), key.qtype, value.counter);
858 }
859
860 res = bpf_get_next_key(map.d_fd.getHandle(), &nextKey, &nextKey);
861 }
862 }
863
864 return result;
865 }
866
867 uint64_t BPFFilter::getHits(const ComboAddress& requestor)
868 {
869 CounterAndActionValue counter;
870
871 if (requestor.isIPv4()) {
872 uint32_t key = htonl(requestor.sin4.sin_addr.s_addr);
873
874 auto maps = d_maps.lock();
875 auto& map = maps->d_v4;
876 int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter);
877 if (res == 0) {
878 return counter.counter;
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
888 auto maps = d_maps.lock();
889 auto& map = maps->d_v6;
890 int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter);
891 if (res == 0) {
892 return counter.counter;
893 }
894 }
895
896 return 0;
897 }
898
899 #else
900
901 BPFFilter::BPFFilter(std::unordered_map<std::string, MapConfiguration>& configs, BPFFilter::MapFormat format, bool external)
902 {
903 }
904
905 void BPFFilter::addSocket(int)
906 {
907 throw std::runtime_error("eBPF support not enabled");
908 }
909
910 void BPFFilter::removeSocket(int)
911 {
912 throw std::runtime_error("eBPF support not enabled");
913 }
914
915 void BPFFilter::block(const ComboAddress&, BPFFilter::MatchAction)
916 {
917 throw std::runtime_error("eBPF support not enabled");
918 }
919
920 void BPFFilter::unblock(const ComboAddress&)
921 {
922 throw std::runtime_error("eBPF support not enabled");
923 }
924
925 void BPFFilter::block(const DNSName&, BPFFilter::MatchAction, uint16_t)
926 {
927 throw std::runtime_error("eBPF support not enabled");
928 }
929
930 void BPFFilter::unblock(const DNSName&, uint16_t)
931 {
932 throw std::runtime_error("eBPF support not enabled");
933 }
934
935 void BPFFilter::addRangeRule(const Netmask&, bool, BPFFilter::MatchAction)
936 {
937 throw std::runtime_error("eBPF support not enabled");
938 }
939 void BPFFilter::rmRangeRule(const Netmask&)
940 {
941 throw std::runtime_error("eBPF support not enabled");
942 }
943
944 std::vector<std::pair<Netmask, CounterAndActionValue>> BPFFilter::getRangeRule(){
945 std::vector<std::pair<Netmask, CounterAndActionValue>> result;
946 return result;
947 }
948 std::vector<std::pair<ComboAddress, uint64_t> > BPFFilter::getAddrStats()
949 {
950 std::vector<std::pair<ComboAddress, uint64_t> > result;
951 return result;
952 }
953
954 std::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
960 uint64_t BPFFilter::getHits(const ComboAddress&)
961 {
962 return 0;
963 }
964 #endif /* HAVE_EBPF */
965
966 bool 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
977 bool BPFFilter::isExternal() const
978 {
979 #ifdef HAVE_EBPF
980 return d_external;
981 #endif /* HAVE_EBPF */
982 return false;
983 }