• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 19741624072

27 Nov 2025 03:45PM UTC coverage: 73.086% (+0.02%) from 73.065%
19741624072

Pull #16570

github

web-flow
Merge 08a2cdb1d into f94a3f63f
Pull Request #16570: rec: rewrite all unwrap calls in web.rs

38523 of 63408 branches covered (60.75%)

Branch coverage included in aggregate %.

128044 of 164496 relevant lines covered (77.84%)

6531485.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

47.53
/pdns/dnsdistdist/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
{
134✔
38
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
39
  return (__u64)(unsigned long)ptr;
134✔
40
}
134✔
41

42
/* these can be static as they are not declared in libbpf.h: */
43
static int bpf_pin_map(int descriptor, const std::string& path)
44
{
×
45
  union bpf_attr attr;
×
46
  memset(&attr, 0, sizeof(attr));
×
47
  attr.bpf_fd = descriptor;
×
48
  attr.pathname = ptr_to_u64(path.c_str());
×
49
  return syscall(SYS_bpf, BPF_OBJ_PIN, &attr, sizeof(attr));
×
50
}
×
51

52
static int bpf_load_pinned_map(const std::string& path)
53
{
×
54
  union bpf_attr attr;
×
55
  memset(&attr, 0, sizeof(attr));
×
56
  attr.pathname = ptr_to_u64(path.c_str());
×
57
  return syscall(SYS_bpf, BPF_OBJ_GET, &attr, sizeof(attr));
×
58
}
×
59

60
static void bpf_check_map_sizes(int descriptor, uint32_t expectedKeySize, uint32_t expectedValueSize)
61
{
×
62
  struct bpf_map_info info;
×
63
  uint32_t info_len = sizeof(info);
×
64
  memset(&info, 0, sizeof(info));
×
65

66
  union bpf_attr attr;
×
67
  memset(&attr, 0, sizeof(attr));
×
68
  attr.info.bpf_fd = descriptor;
×
69
  attr.info.info_len = info_len;
×
70
  attr.info.info = ptr_to_u64(&info);
×
71

72
  int err = syscall(SYS_bpf, BPF_OBJ_GET_INFO_BY_FD, &attr, sizeof(attr));
×
73
  if (err != 0) {
×
74
    throw std::runtime_error("Error checking the size of eBPF map: " + stringerror());
×
75
  }
×
76
  if (info_len != sizeof(info)) {
×
77
    throw std::runtime_error("Error checking the size of eBPF map: invalid info size returned");
×
78
  }
×
79
  if (info.key_size != expectedKeySize) {
×
80
    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) + ")");
×
81
  }
×
82
  if (info.value_size != expectedValueSize) {
×
83
    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) + ")");
×
84
  }
×
85
}
×
86

87
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
88
static int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size,
89
                          int max_entries, int map_flags)
90
{
16✔
91
  union bpf_attr attr;
16✔
92
  memset(&attr, 0, sizeof(attr));
16✔
93
  attr.map_type = map_type;
16✔
94
  attr.key_size = key_size;
16✔
95
  attr.value_size = value_size;
16✔
96
  attr.max_entries = max_entries;
16✔
97
  attr.map_flags = map_flags;
16✔
98
  return syscall(SYS_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
16✔
99
}
16✔
100

101
static int bpf_update_elem(int descriptor, void* key, void* value, unsigned long long flags)
102
{
15✔
103
  union bpf_attr attr;
15✔
104
  memset(&attr, 0, sizeof(attr));
15✔
105
  attr.map_fd = descriptor;
15✔
106
  attr.key = ptr_to_u64(key);
15✔
107
  attr.value = ptr_to_u64(value);
15✔
108
  attr.flags = flags;
15✔
109
  return syscall(SYS_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
15✔
110
}
15✔
111

112
static int bpf_lookup_elem(int descriptor, void* key, void* value)
113
{
19✔
114
  union bpf_attr attr;
19✔
115
  memset(&attr, 0, sizeof(attr));
19✔
116
  attr.map_fd = descriptor;
19✔
117
  attr.key = ptr_to_u64(key);
19✔
118
  attr.value = ptr_to_u64(value);
19✔
119
  return syscall(SYS_bpf, BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
19✔
120
}
19✔
121

122
static int bpf_delete_elem(int descriptor, void* key)
123
{
10✔
124
  union bpf_attr attr;
10✔
125
  memset(&attr, 0, sizeof(attr));
10✔
126
  attr.map_fd = descriptor;
10✔
127
  attr.key = ptr_to_u64(key);
10✔
128
  return syscall(SYS_bpf, BPF_MAP_DELETE_ELEM, &attr, sizeof(attr));
10✔
129
}
10✔
130

131
static int bpf_get_next_key(int descriptor, void* key, void* next_key)
132
{
14✔
133
  union bpf_attr attr;
14✔
134
  memset(&attr, 0, sizeof(attr));
14✔
135
  attr.map_fd = descriptor;
14✔
136
  attr.key = ptr_to_u64(key);
14✔
137
  attr.next_key = ptr_to_u64(next_key);
14✔
138
  return syscall(SYS_bpf, BPF_MAP_GET_NEXT_KEY, &attr, sizeof(attr));
14✔
139
}
14✔
140

141
static int bpf_prog_load(enum bpf_prog_type prog_type,
142
                         const struct bpf_insn* insns, size_t prog_len,
143
                         const char* license, int kern_version)
144
{
8✔
145
  char log_buf[65535];
8✔
146
  union bpf_attr attr;
8✔
147
  memset(&attr, 0, sizeof(attr));
8✔
148
  attr.prog_type = prog_type;
8✔
149
  attr.insns = ptr_to_u64((void*)insns);
8✔
150
  attr.insn_cnt = static_cast<int>(prog_len / sizeof(struct bpf_insn));
8✔
151
  attr.license = ptr_to_u64((void*)license);
8✔
152
  attr.log_buf = ptr_to_u64(log_buf);
8✔
153
  attr.log_size = sizeof(log_buf);
8✔
154
  attr.log_level = 1;
8✔
155
  /* assign one field outside of struct init to make sure any
156
   * padding is zero initialized
157
   */
158
  attr.kern_version = kern_version;
8✔
159

160
  long res = syscall(SYS_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
8✔
161
  if (res == -1) {
8✔
162
    if (errno == ENOSPC) {
4!
163
      /* not enough space in the log buffer */
164
      attr.log_level = 0;
4✔
165
      attr.log_size = 0;
4✔
166
      attr.log_buf = ptr_to_u64(nullptr);
4✔
167
      res = syscall(SYS_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
4✔
168
      if (res != -1) {
4!
169
        return res;
4✔
170
      }
4✔
171
    }
4✔
172
    throw std::runtime_error("Error loading BPF program: (" + stringerror() + "):\n" + std::string(log_buf));
×
173
  }
4✔
174
  return res;
4✔
175
}
8✔
176

177
struct KeyV6
178
{
179
  uint8_t src[16];
180
};
181

182
struct QNameKey
183
{
184
  uint8_t qname[255];
185
};
186

187
struct QNameAndQTypeKey
188
{
189
  uint8_t qname[255];
190
  uint16_t qtype;
191
};
192

193
struct QNameValue
194
{
195
  uint64_t counter{0};
196
  uint16_t qtype{0};
197
};
198

199
BPFFilter::Map::Map(BPFFilter::MapConfiguration config, BPFFilter::MapFormat format) :
200
  d_config(std::move(config))
16✔
201
{
16✔
202
  if (d_config.d_type == BPFFilter::MapType::Filters) {
16✔
203
    /* special case, this is a map of eBPF programs */
204
    d_fd = FDWrapper(bpf_create_map(BPF_MAP_TYPE_PROG_ARRAY, sizeof(uint32_t), sizeof(uint32_t), d_config.d_maxItems, 0));
4✔
205
    if (d_fd.getHandle() == -1) {
4!
206
      throw std::runtime_error("Error creating a BPF program map of size " + std::to_string(d_config.d_maxItems) + ": " + stringerror());
×
207
    }
×
208
  }
4✔
209
  else {
12✔
210
    int keySize = 0;
12✔
211
    int valueSize = 0;
12✔
212
    int flags = 0;
12✔
213
    bpf_map_type type = BPF_MAP_TYPE_HASH;
12✔
214
    if (format == MapFormat::Legacy) {
12!
215
      switch (d_config.d_type) {
12✔
216
      case MapType::IPv4:
4✔
217
        keySize = sizeof(uint32_t);
4✔
218
        valueSize = sizeof(uint64_t);
4✔
219
        break;
4✔
220
      case MapType::IPv6:
4✔
221
        keySize = sizeof(KeyV6);
4✔
222
        valueSize = sizeof(uint64_t);
4✔
223
        break;
4✔
224
      case MapType::QNames:
4✔
225
        keySize = sizeof(QNameKey);
4✔
226
        valueSize = sizeof(QNameValue);
4✔
227
        break;
4✔
228
      default:
×
229
        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?");
×
230
      }
12✔
231
    }
12✔
232
    else {
×
233
      switch (d_config.d_type) {
×
234
      case MapType::IPv4:
×
235
        keySize = sizeof(uint32_t);
×
236
        valueSize = sizeof(CounterAndActionValue);
×
237
        break;
×
238
      case MapType::IPv6:
×
239
        keySize = sizeof(KeyV6);
×
240
        valueSize = sizeof(CounterAndActionValue);
×
241
        break;
×
242
      case MapType::CIDR4:
×
243
        keySize = sizeof(CIDR4);
×
244
        valueSize = sizeof(CounterAndActionValue);
×
245
        flags = BPF_F_NO_PREALLOC;
×
246
        type = BPF_MAP_TYPE_LPM_TRIE;
×
247
        break;
×
248
      case MapType::CIDR6:
×
249
        keySize = sizeof(CIDR6);
×
250
        valueSize = sizeof(CounterAndActionValue);
×
251
        flags = BPF_F_NO_PREALLOC;
×
252
        type = BPF_MAP_TYPE_LPM_TRIE;
×
253
        break;
×
254
      case MapType::QNames:
×
255
        keySize = sizeof(QNameAndQTypeKey);
×
256
        valueSize = sizeof(CounterAndActionValue);
×
257
        break;
×
258
      default:
×
259
        throw std::runtime_error("Unsupported eBPF map type: " + std::to_string(static_cast<uint8_t>(d_config.d_type)));
×
260
      }
×
261
    }
×
262

263
    if (!d_config.d_pinnedPath.empty()) {
12!
264
      /* try to load */
265
      d_fd = FDWrapper(bpf_load_pinned_map(d_config.d_pinnedPath));
×
266
      if (d_fd.getHandle() != -1) {
×
267
        /* sanity checks: key and value size */
268
        bpf_check_map_sizes(d_fd.getHandle(), keySize, valueSize);
×
269
        switch (d_config.d_type) {
×
270
        case MapType::IPv4: {
×
271
          uint32_t key = 0;
×
272
          while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
×
273
            ++d_count;
×
274
          }
×
275
          break;
×
276
        }
×
277
        case MapType::IPv6: {
×
278
          KeyV6 key;
×
279
          memset(&key, 0, sizeof(key));
×
280
          while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
×
281
            ++d_count;
×
282
          }
×
283
          break;
×
284
        }
×
285
        case MapType::CIDR4: {
×
286
          CIDR4 key;
×
287
          memset(&key, 0, sizeof(key));
×
288
          while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
×
289
            ++d_count;
×
290
          }
×
291
          break;
×
292
        }
×
293
        case MapType::CIDR6: {
×
294
          CIDR6 key;
×
295
          memset(&key, 0, sizeof(key));
×
296
          while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
×
297
            ++d_count;
×
298
          }
×
299
          break;
×
300
        }
×
301
        case MapType::QNames: {
×
302
          if (format == MapFormat::Legacy) {
×
303
            QNameKey key;
×
304
            memset(&key, 0, sizeof(key));
×
305
            while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
×
306
              ++d_count;
×
307
            }
×
308
          }
×
309
          else {
×
310
            QNameAndQTypeKey key;
×
311
            memset(&key, 0, sizeof(key));
×
312
            while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) {
×
313
              ++d_count;
×
314
            }
×
315
          }
×
316
          break;
×
317
        }
×
318

319
        default:
×
320
          throw std::runtime_error("Unsupported eBPF map type: " + std::to_string(static_cast<uint8_t>(d_config.d_type)));
×
321
        }
×
322
      }
×
323
    }
×
324

325
    if (d_fd.getHandle() == -1) {
12!
326
      d_fd = FDWrapper(bpf_create_map(type, keySize, valueSize, static_cast<int>(d_config.d_maxItems), flags));
12✔
327
      if (d_fd.getHandle() == -1) {
12!
328
        throw std::runtime_error("Error creating a BPF map of size " + std::to_string(d_config.d_maxItems) + ": " + stringerror());
×
329
      }
×
330

331
      if (!d_config.d_pinnedPath.empty()) {
12!
332
        if (bpf_pin_map(d_fd.getHandle(), d_config.d_pinnedPath) != 0) {
×
333
          throw std::runtime_error("Unable to pin map to path '" + d_config.d_pinnedPath + "': " + stringerror());
×
334
        }
×
335
      }
×
336
    }
12✔
337
  }
12✔
338
}
16✔
339

340
static FDWrapper loadProgram(const struct bpf_insn* filter, size_t filterSize)
341
{
8✔
342
  auto descriptor = FDWrapper(bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER,
8✔
343
                                            filter,
8✔
344
                                            filterSize,
8✔
345
                                            "GPL",
8✔
346
                                            0));
8✔
347
  if (descriptor.getHandle() == -1) {
8!
348
    throw std::runtime_error("error loading BPF filter: " + stringerror());
×
349
  }
×
350
  return descriptor;
8✔
351
}
8✔
352

353
BPFFilter::BPFFilter(std::unordered_map<std::string, MapConfiguration>& configs, BPFFilter::MapFormat format, bool external) :
354
  d_mapFormat(format), d_external(external)
4✔
355
{
4✔
356
  if (d_mapFormat != BPFFilter::MapFormat::Legacy && !d_external) {
4!
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;
4✔
361
  if (getrlimit(RLIMIT_MEMLOCK, &old_limit) != 0) {
4!
362
    throw std::runtime_error("Unable to get memory lock limit: " + stringerror());
×
363
  }
×
364

365
  const rlim_t new_limit_size = 1024 * 1024;
4✔
366

367
  /* Check if the current soft memlock limit is at least the limit */
368
  if (old_limit.rlim_cur < new_limit_size) {
4!
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();
4✔
381

382
  maps->d_v4 = BPFFilter::Map(configs["ipv4"], d_mapFormat);
4✔
383
  maps->d_v6 = BPFFilter::Map(configs["ipv6"], d_mapFormat);
4✔
384
  maps->d_qnames = BPFFilter::Map(configs["qnames"], d_mapFormat);
4✔
385

386
  if (d_mapFormat != BPFFilter::MapFormat::Legacy) {
4!
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) {
4!
392
    BPFFilter::MapConfiguration filters;
4✔
393
    filters.d_maxItems = 1;
4✔
394
    filters.d_type = BPFFilter::MapType::Filters;
4✔
395
    maps->d_filters = BPFFilter::Map(std::move(filters), d_mapFormat);
4✔
396

397
    const struct bpf_insn main_filter[] = {
4✔
398
#include "bpf-filter.main.ebpf"
4✔
399
    };
4✔
400

401
    // ABANDON EVERY HOPE - SCARY CODE STARTS HERE
402

403
    // This EBF program is huge, and unfortunately not constant; this causes
404
    // the compiler to emit a lot of code (and eat a lot of memory to do so).
405
    // Therefore we include an incomplete but constant version of the program
406
    // and patch it locally, relying upon the fact that there is only one place
407
    // to change.
408
    // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays)
409
    struct bpf_insn qname_filter[] = {
4✔
410
#include "bpf-filter.qname.ebpf"
4✔
411
    };
4✔
412

413
    // The program above contains
414
    //   BPF_LD_MAP_FD(BPF_REG_1,0)
415
    // instead of
416
    //   BPF_LD_MAP_FD(BPF_REG_1,maps->d_qnames.d_fd.getHandle())
417
    // and this is the only use of BPF_LD_MAP_FD in the program.
418
    // We will search for that instruction, relying upon the fact that,
419
    // in that particular program, there is only one such instruction.
420
    {
4✔
421
      unsigned int pos{0};
4✔
422
      unsigned int limit{sizeof(qname_filter) / sizeof(struct bpf_insn)};
4✔
423
      for (; pos < limit; ++pos) {
16,320!
424
        if (qname_filter[pos].code == (BPF_LD | BPF_DW | BPF_IMM)) { // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)
16,320✔
425
          // We have found our instruction.
426
          break;
4✔
427
        }
4✔
428
      }
16,320✔
429
      // BPF_LD_MAP_FP actually is a sequence of two bpf instructions,
430
      // because it loads a 64-bit value. So it can't be the last
431
      // instruction either...
432
      if (pos >= limit - 1) {
4!
433
        throw std::runtime_error("Assumption in the layout of the eBPF filter program no longer stands");
×
434
      }
×
435
      auto data = static_cast<__u64>(maps->d_qnames.d_fd.getHandle());
4✔
436
      qname_filter[pos].imm = static_cast<__s32>(data); // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)
4✔
437
      qname_filter[pos + 1].imm = static_cast<__s32>(data >> 32); // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)
4✔
438
    }
4✔
439

440
    // SCARY CODE ENDS HERE
441

442
    try {
4✔
443
      d_mainfilter = loadProgram(main_filter,
4✔
444
                                 sizeof(main_filter));
4✔
445
    }
4✔
446
    catch (const std::exception& e) {
4✔
447
      throw std::runtime_error("Error load the main eBPF filter: " + std::string(e.what()));
×
448
    }
×
449

450
    try {
4✔
451
      d_qnamefilter = loadProgram(qname_filter,
4✔
452
                                  sizeof(qname_filter));
4✔
453
    }
4✔
454
    catch (const std::exception& e) {
4✔
455
      throw std::runtime_error("Error load the qname eBPF filter: " + std::string(e.what()));
×
456
    }
×
457

458
    uint32_t key = 0;
4✔
459
    int qnamefd = d_qnamefilter.getHandle();
4✔
460
    int res = bpf_update_elem(maps->d_filters.d_fd.getHandle(), &key, &qnamefd, BPF_ANY);
4✔
461
    if (res != 0) {
4!
462
      throw std::runtime_error("Error updating BPF filters map: " + stringerror());
×
463
    }
×
464
  }
4✔
465
}
4✔
466

467
void BPFFilter::addSocket(int sock)
468
{
6✔
469
  int descriptor = d_mainfilter.getHandle();
6✔
470
  int res = setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &descriptor, sizeof(descriptor));
6✔
471

472
  if (res != 0) {
6!
473
    throw std::runtime_error("Error attaching BPF filter to this socket: " + stringerror());
×
474
  }
×
475
}
6✔
476

477
void BPFFilter::removeSocket(int sock)
478
{
×
479
  int descriptor = d_mainfilter.getHandle();
×
480
  int res = setsockopt(sock, SOL_SOCKET, SO_DETACH_BPF, &descriptor, sizeof(descriptor));
×
481

482
  if (res != 0) {
×
483
    throw std::runtime_error("Error detaching BPF filter from this socket: " + stringerror());
×
484
  }
×
485
}
×
486

487
void BPFFilter::block(const ComboAddress& addr, BPFFilter::MatchAction action)
488
{
5✔
489
  CounterAndActionValue value;
5✔
490
  value.counter = 0;
5✔
491
  value.action = action;
5✔
492

493
  int res = 0;
5✔
494
  if (addr.isIPv4()) {
5✔
495
    uint32_t key = htonl(addr.sin4.sin_addr.s_addr);
3✔
496
    auto maps = d_maps.lock();
3✔
497
    auto& map = maps->d_v4;
3✔
498
    if (map.d_count >= map.d_config.d_maxItems) {
3!
499
      throw std::runtime_error("Table full when trying to block " + addr.toString());
×
500
    }
×
501

502
    res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
3✔
503
    if (res != -1) {
3!
504
      throw std::runtime_error("Trying to block an already blocked address: " + addr.toString());
×
505
    }
×
506

507
    res = bpf_update_elem(map.d_fd.getHandle(), &key, &value, BPF_NOEXIST);
3✔
508
    if (res == 0) {
3!
509
      ++map.d_count;
3✔
510
    }
3✔
511
  }
3✔
512
  else if (addr.isIPv6()) {
2!
513
    uint8_t key[16];
2✔
514
    static_assert(sizeof(addr.sin6.sin6_addr.s6_addr) == sizeof(key), "POSIX mandates s6_addr to be an array of 16 uint8_t");
2✔
515
    for (size_t idx = 0; idx < sizeof(key); idx++) {
34✔
516
      key[idx] = addr.sin6.sin6_addr.s6_addr[idx];
32✔
517
    }
32✔
518

519
    auto maps = d_maps.lock();
2✔
520
    auto& map = maps->d_v6;
2✔
521
    if (map.d_count >= map.d_config.d_maxItems) {
2!
522
      throw std::runtime_error("Table full when trying to block " + addr.toString());
×
523
    }
×
524

525
    res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
2✔
526
    if (res != -1) {
2!
527
      throw std::runtime_error("Trying to block an already blocked address: " + addr.toString());
×
528
    }
×
529

530
    res = bpf_update_elem(map.d_fd.getHandle(), key, &value, BPF_NOEXIST);
2✔
531
    if (res == 0) {
2!
532
      map.d_count++;
2✔
533
    }
2✔
534
  }
2✔
535

536
  if (res != 0) {
5!
537
    throw std::runtime_error("Error adding blocked address " + addr.toString() + ": " + stringerror());
×
538
  }
×
539
}
5✔
540

541
void BPFFilter::unblock(const ComboAddress& addr)
542
{
8✔
543
  int res = 0;
8✔
544
  if (addr.isIPv4()) {
8✔
545
    uint32_t key = htonl(addr.sin4.sin_addr.s_addr);
6✔
546
    auto maps = d_maps.lock();
6✔
547
    auto& map = maps->d_v4;
6✔
548
    res = bpf_delete_elem(map.d_fd.getHandle(), &key);
6✔
549
    if (res == 0) {
6✔
550
      --map.d_count;
3✔
551
    }
3✔
552
  }
6✔
553
  else if (addr.isIPv6()) {
2!
554
    uint8_t key[16];
2✔
555
    static_assert(sizeof(addr.sin6.sin6_addr.s6_addr) == sizeof(key), "POSIX mandates s6_addr to be an array of 16 uint8_t");
2✔
556
    for (size_t idx = 0; idx < sizeof(key); idx++) {
34✔
557
      key[idx] = addr.sin6.sin6_addr.s6_addr[idx];
32✔
558
    }
32✔
559

560
    auto maps = d_maps.lock();
2✔
561
    auto& map = maps->d_v6;
2✔
562
    res = bpf_delete_elem(map.d_fd.getHandle(), key);
2✔
563
    if (res == 0) {
2!
564
      --map.d_count;
2✔
565
    }
2✔
566
  }
2✔
567

568
  if (res != 0) {
8✔
569
    throw std::runtime_error("Error removing blocked address " + addr.toString() + ": " + stringerror());
3✔
570
  }
3✔
571
}
8✔
572

573
void BPFFilter::addRangeRule(const Netmask& addr, bool force, BPFFilter::MatchAction action)
574
{
×
575
  CounterAndActionValue value;
×
576

577
  int res = 0;
×
578
  if (addr.isIPv4()) {
×
579
    CIDR4 key(addr);
×
580
    auto maps = d_maps.lock();
×
581
    auto& map = maps->d_cidr4;
×
582
    if (map.d_fd.getHandle() == -1) {
×
583
      throw std::runtime_error("Trying to use an unsupported map type, likely adding a range to a legacy eBPF program");
×
584
    }
×
585
    if (map.d_count >= map.d_config.d_maxItems) {
×
586
      throw std::runtime_error("Table full when trying to add this rule: " + addr.toString());
×
587
    }
×
588

589
    res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
×
590
    if (((res != -1 && value.action == action) || (res == -1 && value.action == BPFFilter::MatchAction::Pass)) && !force) {
×
591
      throw std::runtime_error("Trying to add a useless rule: " + addr.toString());
×
592
    }
×
593

594
    value.counter = 0;
×
595
    value.action = action;
×
596

597
    res = bpf_update_elem(map.d_fd.getHandle(), &key, &value, force ? BPF_ANY : BPF_NOEXIST);
×
598
    if (res == 0) {
×
599
      ++map.d_count;
×
600
    }
×
601
  }
×
602
  else if (addr.isIPv6()) {
×
603
    CIDR6 key(addr);
×
604

605
    auto maps = d_maps.lock();
×
606
    auto& map = maps->d_cidr6;
×
607
    if (map.d_fd.getHandle() == -1) {
×
608
      throw std::runtime_error("Trying to use an unsupported map type, likely adding a range to a legacy eBPF program");
×
609
    }
×
610
    if (map.d_count >= map.d_config.d_maxItems) {
×
611
      throw std::runtime_error("Table full when trying to add this rule: " + addr.toString());
×
612
    }
×
613

614
    res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
×
615
    if (((res != -1 && value.action == action) || (res == -1 && value.action == BPFFilter::MatchAction::Pass)) && !force) {
×
616
      throw std::runtime_error("Trying to add a useless rule: " + addr.toString());
×
617
    }
×
618

619
    value.counter = 0;
×
620
    value.action = action;
×
621

622
    res = bpf_update_elem(map.d_fd.getHandle(), &key, &value, BPF_NOEXIST);
×
623
    if (res == 0) {
×
624
      map.d_count++;
×
625
    }
×
626
  }
×
627

628
  if (res != 0) {
×
629
    throw std::runtime_error("Error adding this rule: " + addr.toString() + ": " + stringerror());
×
630
  }
×
631
}
×
632

633
void BPFFilter::rmRangeRule(const Netmask& addr)
634
{
×
635
  int res = 0;
×
636
  CounterAndActionValue value;
×
637
  value.counter = 0;
×
638
  value.action = MatchAction::Pass;
×
639
  if (addr.isIPv4()) {
×
640
    CIDR4 key(addr);
×
641
    auto maps = d_maps.lock();
×
642
    auto& map = maps->d_cidr4;
×
643
    if (map.d_fd.getHandle() == -1) {
×
644
      throw std::runtime_error("Trying to use an unsupported map type, likely adding a range to a legacy eBPF program");
×
645
    }
×
646
    res = bpf_delete_elem(map.d_fd.getHandle(), &key);
×
647
    if (res == 0) {
×
648
      --map.d_count;
×
649
    }
×
650
    else {
×
651
      throw std::runtime_error("Cannot remove '" + addr.toString() + "': No such rule");
×
652
    }
×
653
  }
×
654
  else if (addr.isIPv6()) {
×
655
    CIDR6 key(addr);
×
656

657
    auto maps = d_maps.lock();
×
658
    auto& map = maps->d_cidr6;
×
659
    if (map.d_fd.getHandle() == -1) {
×
660
      throw std::runtime_error("Trying to use an unsupported map type, likely adding a range to a legacy eBPF program");
×
661
    }
×
662
    res = bpf_delete_elem(map.d_fd.getHandle(), &key);
×
663
    if (res == 0) {
×
664
      --map.d_count;
×
665
    }
×
666
    else {
×
667
      throw std::runtime_error("Cannot remove '" + addr.toString() + "': No such rule");
×
668
    }
×
669
  }
×
670

671
  if (res != 0) {
×
672
    throw std::runtime_error("Error removing this rule: " + addr.toString() + ": " + stringerror());
×
673
  }
×
674
}
×
675

676
void BPFFilter::block(const DNSName& qname, BPFFilter::MatchAction action, uint16_t qtype)
677
{
6✔
678
  CounterAndActionValue cadvalue;
6✔
679
  QNameValue qvalue;
6✔
680
  void* value = nullptr;
6✔
681

682
  if (d_external) {
6!
683
    cadvalue.counter = 0;
×
684
    cadvalue.action = action;
×
685
    value = &cadvalue;
×
686
  }
×
687
  else {
6✔
688
    qvalue.counter = 0;
6✔
689
    qvalue.qtype = qtype;
6✔
690
    value = &qvalue;
6✔
691
  }
6✔
692

693
  QNameAndQTypeKey key;
6✔
694
  memset(&key, 0, sizeof(key));
6✔
695

696
  std::string keyStr = qname.toDNSStringLC();
6✔
697
  if (keyStr.size() > sizeof(key.qname)) {
6!
698
    throw std::runtime_error("Invalid QName to block " + qname.toLogString());
×
699
  }
×
700
  memcpy(key.qname, keyStr.c_str(), keyStr.size());
6✔
701
  key.qtype = qtype;
6✔
702

703
  {
6✔
704
    auto maps = d_maps.lock();
6✔
705
    auto& map = maps->d_qnames;
6✔
706
    if (map.d_count >= map.d_config.d_maxItems) {
6!
707
      throw std::runtime_error("Table full when trying to block " + qname.toLogString());
×
708
    }
×
709

710
    int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, value);
6✔
711
    if (res != -1) {
6!
712
      throw std::runtime_error("Trying to block an already blocked qname: " + qname.toLogString());
×
713
    }
×
714
    res = bpf_update_elem(map.d_fd.getHandle(), &key, value, BPF_NOEXIST);
6✔
715
    if (res == 0) {
6!
716
      ++map.d_count;
6✔
717
    }
6✔
718

719
    if (res != 0) {
6!
720
      throw std::runtime_error("Error adding blocked qname " + qname.toLogString() + ": " + stringerror());
×
721
    }
×
722
  }
6✔
723
}
6✔
724

725
void BPFFilter::unblock(const DNSName& qname, uint16_t qtype)
726
{
2✔
727
  QNameAndQTypeKey key;
2✔
728
  memset(&key, 0, sizeof(key));
2✔
729
  std::string keyStr = qname.toDNSStringLC();
2✔
730

731
  if (keyStr.size() > sizeof(key.qname)) {
2!
732
    throw std::runtime_error("Invalid QName to block " + qname.toLogString());
×
733
  }
×
734
  memcpy(key.qname, keyStr.c_str(), keyStr.size());
2✔
735
  key.qtype = qtype;
2✔
736

737
  {
2✔
738
    auto maps = d_maps.lock();
2✔
739
    auto& map = maps->d_qnames;
2✔
740
    int res = bpf_delete_elem(map.d_fd.getHandle(), &key);
2✔
741
    if (res == 0) {
2!
742
      --map.d_count;
2✔
743
    }
2✔
744
    else {
×
745
      throw std::runtime_error("Error removing qname address " + qname.toLogString() + ": " + stringerror());
×
746
    }
×
747
  }
2✔
748
}
2✔
749

750
std::vector<std::pair<ComboAddress, uint64_t>> BPFFilter::getAddrStats()
751
{
2✔
752
  std::vector<std::pair<ComboAddress, uint64_t>> result;
2✔
753
  {
2✔
754
    auto maps = d_maps.lock();
2✔
755
    result.reserve(maps->d_v4.d_count + maps->d_v6.d_count);
2✔
756
  }
2✔
757

758
  sockaddr_in v4Addr{};
2✔
759
  memset(&v4Addr, 0, sizeof(v4Addr));
2✔
760
  v4Addr.sin_family = AF_INET;
2✔
761

762
  uint32_t v4Key = 0;
2✔
763
  uint32_t nextV4Key{};
2✔
764
  CounterAndActionValue value{};
2✔
765

766
  std::array<uint8_t, 16> v6Key{};
2✔
767
  std::array<uint8_t, 16> nextV6Key{};
2✔
768
  sockaddr_in6 v6Addr{};
2✔
769
  memset(&v6Addr, 0, sizeof(v6Addr));
2✔
770
  v6Addr.sin6_family = AF_INET6;
2✔
771

772
  static_assert(sizeof(v6Addr.sin6_addr.s6_addr) == v6Key.size(), "POSIX mandates s6_addr to be an array of 16 uint8_t");
2✔
773
  memset(&v6Key, 0, sizeof(v6Key));
2✔
774

775
  auto maps = d_maps.lock();
2✔
776

777
  {
2✔
778
    auto& map = maps->d_v4;
2✔
779
    int res = bpf_get_next_key(map.d_fd.getHandle(), &v4Key, &nextV4Key);
2✔
780

781
    while (res == 0) {
2!
782
      v4Key = nextV4Key;
×
783
      if (bpf_lookup_elem(map.d_fd.getHandle(), &v4Key, &value) == 0) {
×
784
        v4Addr.sin_addr.s_addr = ntohl(v4Key);
×
785
        result.emplace_back(ComboAddress(&v4Addr), value.counter);
×
786
      }
×
787

788
      res = bpf_get_next_key(map.d_fd.getHandle(), &v4Key, &nextV4Key);
×
789
    }
×
790
  }
2✔
791

792
  {
2✔
793
    auto& map = maps->d_v6;
2✔
794
    int res = bpf_get_next_key(map.d_fd.getHandle(), v6Key.data(), nextV6Key.data());
2✔
795

796
    while (res == 0) {
4✔
797
      if (bpf_lookup_elem(map.d_fd.getHandle(), nextV6Key.data(), &value) == 0) {
2!
798
        memcpy(&v6Addr.sin6_addr.s6_addr, nextV6Key.data(), nextV6Key.size());
2✔
799

800
        result.emplace_back(ComboAddress(&v6Addr), value.counter);
2✔
801
      }
2✔
802

803
      res = bpf_get_next_key(map.d_fd.getHandle(), nextV6Key.data(), nextV6Key.data());
2✔
804
    }
2✔
805
  }
2✔
806

807
  return result;
2✔
808
}
2✔
809

810
std::vector<std::pair<Netmask, CounterAndActionValue>> BPFFilter::getRangeRule()
811
{
2✔
812
  CIDR4 cidr4[2];
2✔
813
  CIDR6 cidr6[2];
2✔
814
  std::vector<std::pair<Netmask, CounterAndActionValue>> result;
2✔
815

816
  sockaddr_in v4Addr;
2✔
817
  sockaddr_in6 v6Addr;
2✔
818
  CounterAndActionValue value;
2✔
819

820
  memset(cidr4, 0, sizeof(cidr4));
2✔
821
  memset(cidr6, 0, sizeof(cidr6));
2✔
822
  memset(&v4Addr, 0, sizeof(v4Addr));
2✔
823
  memset(&v6Addr, 0, sizeof(v6Addr));
2✔
824
  v4Addr.sin_family = AF_INET;
2✔
825
  v6Addr.sin6_family = AF_INET6;
2✔
826
  auto maps = d_maps.lock();
2✔
827
  result.reserve(maps->d_cidr4.d_count + maps->d_cidr6.d_count);
2✔
828
  {
2✔
829
    auto& map = maps->d_cidr4;
2✔
830
    int res = bpf_get_next_key(map.d_fd.getHandle(), &cidr4[0], &cidr4[1]);
2✔
831
    while (res == 0) {
2!
832
      if (bpf_lookup_elem(map.d_fd.getHandle(), &cidr4[1], &value) == 0) {
×
833
        v4Addr.sin_addr.s_addr = cidr4[1].addr.s_addr;
×
834
        result.emplace_back(Netmask(&v4Addr, cidr4[1].cidr), value);
×
835
      }
×
836

837
      res = bpf_get_next_key(map.d_fd.getHandle(), &cidr4[1], &cidr4[1]);
×
838
    }
×
839
  }
2✔
840

841
  {
2✔
842
    auto& map = maps->d_cidr6;
2✔
843
    int res = bpf_get_next_key(map.d_fd.getHandle(), &cidr6[0], &cidr6[1]);
2✔
844
    while (res == 0) {
2!
845
      if (bpf_lookup_elem(map.d_fd.getHandle(), &cidr6[1], &value) == 0) {
×
846
        v6Addr.sin6_addr = cidr6[1].addr;
×
847
        result.emplace_back(Netmask(&v6Addr, cidr6[1].cidr), value);
×
848
      }
×
849

850
      res = bpf_get_next_key(map.d_fd.getHandle(), &cidr6[1], &cidr6[1]);
×
851
    }
×
852
  }
2✔
853
  return result;
2✔
854
}
2✔
855

856
std::vector<std::tuple<DNSName, uint16_t, uint64_t>> BPFFilter::getQNameStats()
857
{
2✔
858
  std::vector<std::tuple<DNSName, uint16_t, uint64_t>> result;
2✔
859

860
  if (d_mapFormat == MapFormat::Legacy) {
2!
861
    QNameKey key = {{0}};
2✔
862
    QNameKey nextKey = {{0}};
2✔
863
    QNameValue value;
2✔
864

865
    auto maps = d_maps.lock();
2✔
866
    auto& map = maps->d_qnames;
2✔
867
    result.reserve(map.d_count);
2✔
868
    int res = bpf_get_next_key(map.d_fd.getHandle(), &key, &nextKey);
2✔
869

870
    while (res == 0) {
4✔
871
      if (bpf_lookup_elem(map.d_fd.getHandle(), &nextKey, &value) == 0) {
2!
872
        nextKey.qname[sizeof(nextKey.qname) - 1] = '\0';
2✔
873
        result.emplace_back(DNSName(reinterpret_cast<const char*>(nextKey.qname), sizeof(nextKey.qname), 0, false), value.qtype, value.counter);
2✔
874
      }
2✔
875

876
      res = bpf_get_next_key(map.d_fd.getHandle(), &nextKey, &nextKey);
2✔
877
    }
2✔
878
  }
2✔
879
  else {
×
880
    QNameAndQTypeKey key;
×
881
    QNameAndQTypeKey nextKey;
×
882
    memset(&key, 0, sizeof(key));
×
883
    memset(&nextKey, 0, sizeof(nextKey));
×
884
    CounterAndActionValue value;
×
885

886
    auto maps = d_maps.lock();
×
887
    auto& map = maps->d_qnames;
×
888
    result.reserve(map.d_count);
×
889
    int res = bpf_get_next_key(map.d_fd.getHandle(), &key, &nextKey);
×
890

891
    while (res == 0) {
×
892
      if (bpf_lookup_elem(map.d_fd.getHandle(), &nextKey, &value) == 0) {
×
893
        nextKey.qname[sizeof(nextKey.qname) - 1] = '\0';
×
894
        result.emplace_back(DNSName(reinterpret_cast<const char*>(nextKey.qname), sizeof(nextKey.qname), 0, false), key.qtype, value.counter);
×
895
      }
×
896

897
      res = bpf_get_next_key(map.d_fd.getHandle(), &nextKey, &nextKey);
×
898
    }
×
899
  }
×
900

901
  return result;
2✔
902
}
2✔
903

904
uint64_t BPFFilter::getHits(const ComboAddress& requestor)
905
{
4✔
906
  CounterAndActionValue counter;
4✔
907

908
  if (requestor.isIPv4()) {
4!
909
    uint32_t key = htonl(requestor.sin4.sin_addr.s_addr);
4✔
910

911
    auto maps = d_maps.lock();
4✔
912
    auto& map = maps->d_v4;
4✔
913
    int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter);
4✔
914
    if (res == 0) {
4!
915
      return counter.counter;
4✔
916
    }
4✔
917
  }
4✔
918
  else if (requestor.isIPv6()) {
×
919
    uint8_t key[16];
×
920
    static_assert(sizeof(requestor.sin6.sin6_addr.s6_addr) == sizeof(key), "POSIX mandates s6_addr to be an array of 16 uint8_t");
×
921
    for (size_t idx = 0; idx < sizeof(key); idx++) {
×
922
      key[idx] = requestor.sin6.sin6_addr.s6_addr[idx];
×
923
    }
×
924

925
    auto maps = d_maps.lock();
×
926
    auto& map = maps->d_v6;
×
927
    int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter);
×
928
    if (res == 0) {
×
929
      return counter.counter;
×
930
    }
×
931
  }
×
932

933
  return 0;
×
934
}
4✔
935

936
#else
937

938
BPFFilter::BPFFilter(std::unordered_map<std::string, MapConfiguration>& configs, BPFFilter::MapFormat format, bool external)
939
{
940
  (void)configs;
941
  (void)format;
942
  (void)external;
943
}
944

945
void BPFFilter::addSocket(int)
946
{
947
  throw std::runtime_error("eBPF support not enabled");
948
}
949

950
void BPFFilter::removeSocket(int)
951
{
952
  throw std::runtime_error("eBPF support not enabled");
953
}
954

955
void BPFFilter::block(const ComboAddress&, BPFFilter::MatchAction)
956
{
957
  throw std::runtime_error("eBPF support not enabled");
958
}
959

960
void BPFFilter::unblock(const ComboAddress&)
961
{
962
  throw std::runtime_error("eBPF support not enabled");
963
}
964

965
void BPFFilter::block(const DNSName&, BPFFilter::MatchAction, uint16_t)
966
{
967
  throw std::runtime_error("eBPF support not enabled");
968
}
969

970
void BPFFilter::unblock(const DNSName&, uint16_t)
971
{
972
  throw std::runtime_error("eBPF support not enabled");
973
}
974

975
void BPFFilter::addRangeRule(const Netmask&, bool, BPFFilter::MatchAction)
976
{
977
  throw std::runtime_error("eBPF support not enabled");
978
}
979
void BPFFilter::rmRangeRule(const Netmask&)
980
{
981
  throw std::runtime_error("eBPF support not enabled");
982
}
983

984
std::vector<std::pair<Netmask, CounterAndActionValue>> BPFFilter::getRangeRule()
985
{
986
  std::vector<std::pair<Netmask, CounterAndActionValue>> result;
987
  return result;
988
}
989
std::vector<std::pair<ComboAddress, uint64_t>> BPFFilter::getAddrStats()
990
{
991
  std::vector<std::pair<ComboAddress, uint64_t>> result;
992
  return result;
993
}
994

995
std::vector<std::tuple<DNSName, uint16_t, uint64_t>> BPFFilter::getQNameStats()
996
{
997
  std::vector<std::tuple<DNSName, uint16_t, uint64_t>> result;
998
  return result;
999
}
1000

1001
uint64_t BPFFilter::getHits(const ComboAddress&)
1002
{
1003
  return 0;
1004
}
1005
#endif /* HAVE_EBPF */
1006

1007
bool BPFFilter::supportsMatchAction(MatchAction action) const
1008
{
2✔
1009
#ifdef HAVE_EBPF
2✔
1010
  if (action == BPFFilter::MatchAction::Drop) {
2!
1011
    return true;
2✔
1012
  }
2✔
1013
  return d_mapFormat == BPFFilter::MapFormat::WithActions;
×
1014
#else
1015
  (void)action;
1016
  return false;
1017
#endif /* HAVE_EBPF */
1018
}
2✔
1019

1020
bool BPFFilter::isExternal() const
1021
{
6✔
1022
#ifdef HAVE_EBPF
6✔
1023
  return d_external;
6✔
1024
#endif /* HAVE_EBPF */
×
1025
  return false;
×
1026
}
6✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc