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

PowerDNS / pdns / 13012068652

28 Jan 2025 01:59PM UTC coverage: 64.71% (+0.01%) from 64.699%
13012068652

Pull #14724

github

web-flow
Merge b15562560 into db18c3a17
Pull Request #14724: dnsdist: Add meson support

38328 of 90334 branches covered (42.43%)

Branch coverage included in aggregate %.

361 of 513 new or added lines in 35 files covered. (70.37%)

42 existing lines in 13 files now uncovered.

128150 of 166934 relevant lines covered (76.77%)

4540890.91 hits per line

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

71.87
/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
{
125✔
38
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
39
  return (__u64)(unsigned long)ptr;
125✔
40
}
125✔
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
{
13✔
103
  union bpf_attr attr;
13✔
104
  memset(&attr, 0, sizeof(attr));
13✔
105
  attr.map_fd = descriptor;
13✔
106
  attr.key = ptr_to_u64(key);
13✔
107
  attr.value = ptr_to_u64(value);
13✔
108
  attr.flags = flags;
13✔
109
  return syscall(SYS_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
13✔
110
}
13✔
111

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

122
static int bpf_delete_elem(int descriptor, void* key)
123
{
9✔
124
  union bpf_attr attr;
9✔
125
  memset(&attr, 0, sizeof(attr));
9✔
126
  attr.map_fd = descriptor;
9✔
127
  attr.key = ptr_to_u64(key);
9✔
128
  return syscall(SYS_bpf, BPF_MAP_DELETE_ELEM, &attr, sizeof(attr));
9✔
129
}
9✔
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))
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:
12!
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)
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
    const struct bpf_insn qname_filter[] = {
4✔
402
#include "bpf-filter.qname.ebpf"
4✔
403
    };
4✔
404

405
    try {
4✔
406
      d_mainfilter = loadProgram(main_filter,
4✔
407
                                 sizeof(main_filter));
4✔
408
    }
4✔
409
    catch (const std::exception& e) {
4✔
410
      throw std::runtime_error("Error load the main eBPF filter: " + std::string(e.what()));
411
    }
412

413
    try {
4✔
414
      d_qnamefilter = loadProgram(qname_filter,
4✔
415
                                  sizeof(qname_filter));
4✔
416
    }
4✔
417
    catch (const std::exception& e) {
4✔
418
      throw std::runtime_error("Error load the qname eBPF filter: " + std::string(e.what()));
419
    }
420

421
    uint32_t key = 0;
4✔
422
    int qnamefd = d_qnamefilter.getHandle();
4✔
423
    int res = bpf_update_elem(maps->d_filters.d_fd.getHandle(), &key, &qnamefd, BPF_ANY);
4✔
424
    if (res != 0) {
4!
425
      throw std::runtime_error("Error updating BPF filters map: " + stringerror());
426
    }
427
  }
4✔
428
}
4✔
429

430
void BPFFilter::addSocket(int sock)
431
{
6✔
432
  int descriptor = d_mainfilter.getHandle();
6✔
433
  int res = setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &descriptor, sizeof(descriptor));
6✔
434

435
  if (res != 0) {
6!
436
    throw std::runtime_error("Error attaching BPF filter to this socket: " + stringerror());
437
  }
438
}
6✔
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
{
5✔
452
  CounterAndActionValue value;
5✔
453
  value.counter = 0;
5✔
454
  value.action = action;
5✔
455

456
  int res = 0;
5✔
457
  if (addr.isIPv4()) {
5✔
458
    uint32_t key = htonl(addr.sin4.sin_addr.s_addr);
3✔
459
    auto maps = d_maps.lock();
3✔
460
    auto& map = maps->d_v4;
3✔
461
    if (map.d_count >= map.d_config.d_maxItems) {
3!
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);
3✔
466
    if (res != -1) {
3!
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);
3✔
471
    if (res == 0) {
3!
472
      ++map.d_count;
3✔
473
    }
3✔
474
  }
3✔
475
  else if (addr.isIPv6()) {
2!
476
    uint8_t key[16];
2✔
477
    static_assert(sizeof(addr.sin6.sin6_addr.s6_addr) == sizeof(key), "POSIX mandates s6_addr to be an array of 16 uint8_t");
2✔
478
    for (size_t idx = 0; idx < sizeof(key); idx++) {
34✔
479
      key[idx] = addr.sin6.sin6_addr.s6_addr[idx];
32✔
480
    }
32✔
481

482
    auto maps = d_maps.lock();
2✔
483
    auto& map = maps->d_v6;
2✔
484
    if (map.d_count >= map.d_config.d_maxItems) {
2!
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);
2✔
489
    if (res != -1) {
2!
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);
2✔
494
    if (res == 0) {
2!
495
      map.d_count++;
2✔
496
    }
2✔
497
  }
2✔
498

499
  if (res != 0) {
5!
500
    throw std::runtime_error("Error adding blocked address " + addr.toString() + ": " + stringerror());
501
  }
502
}
5✔
503

504
void BPFFilter::unblock(const ComboAddress& addr)
505
{
7✔
506
  int res = 0;
7✔
507
  if (addr.isIPv4()) {
7✔
508
    uint32_t key = htonl(addr.sin4.sin_addr.s_addr);
5✔
509
    auto maps = d_maps.lock();
5✔
510
    auto& map = maps->d_v4;
5✔
511
    res = bpf_delete_elem(map.d_fd.getHandle(), &key);
5✔
512
    if (res == 0) {
5✔
513
      --map.d_count;
3✔
514
    }
3✔
515
  }
5✔
516
  else if (addr.isIPv6()) {
2!
517
    uint8_t key[16];
2✔
518
    static_assert(sizeof(addr.sin6.sin6_addr.s6_addr) == sizeof(key), "POSIX mandates s6_addr to be an array of 16 uint8_t");
2✔
519
    for (size_t idx = 0; idx < sizeof(key); idx++) {
34✔
520
      key[idx] = addr.sin6.sin6_addr.s6_addr[idx];
32✔
521
    }
32✔
522

523
    auto maps = d_maps.lock();
2✔
524
    auto& map = maps->d_v6;
2✔
525
    res = bpf_delete_elem(map.d_fd.getHandle(), key);
2✔
526
    if (res == 0) {
2!
527
      --map.d_count;
2✔
528
    }
2✔
529
  }
2✔
530

531
  if (res != 0) {
7✔
532
    throw std::runtime_error("Error removing blocked address " + addr.toString() + ": " + stringerror());
2✔
533
  }
2✔
534
}
7✔
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
{
4✔
641
  CounterAndActionValue cadvalue;
4✔
642
  QNameValue qvalue;
4✔
643
  void* value = nullptr;
4✔
644

645
  if (d_external) {
4!
646
    cadvalue.counter = 0;
647
    cadvalue.action = action;
648
    value = &cadvalue;
649
  }
650
  else {
4✔
651
    qvalue.counter = 0;
4✔
652
    qvalue.qtype = qtype;
4✔
653
    value = &qvalue;
4✔
654
  }
4✔
655

656
  QNameAndQTypeKey key;
4✔
657
  memset(&key, 0, sizeof(key));
4✔
658

659
  std::string keyStr = qname.toDNSStringLC();
4✔
660
  if (keyStr.size() > sizeof(key.qname)) {
4!
661
    throw std::runtime_error("Invalid QName to block " + qname.toLogString());
662
  }
663
  memcpy(key.qname, keyStr.c_str(), keyStr.size());
4✔
664
  key.qtype = qtype;
4✔
665

666
  {
4✔
667
    auto maps = d_maps.lock();
4✔
668
    auto& map = maps->d_qnames;
4✔
669
    if (map.d_count >= map.d_config.d_maxItems) {
4!
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);
4✔
674
    if (res != -1) {
4!
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);
4✔
678
    if (res == 0) {
4!
679
      ++map.d_count;
4✔
680
    }
4✔
681

682
    if (res != 0) {
4!
683
      throw std::runtime_error("Error adding blocked qname " + qname.toLogString() + ": " + stringerror());
684
    }
685
  }
4✔
686
}
4✔
687

688
void BPFFilter::unblock(const DNSName& qname, uint16_t qtype)
689
{
2✔
690
  QNameAndQTypeKey key;
2✔
691
  memset(&key, 0, sizeof(key));
2✔
692
  std::string keyStr = qname.toDNSStringLC();
2✔
693

694
  if (keyStr.size() > sizeof(key.qname)) {
2!
695
    throw std::runtime_error("Invalid QName to block " + qname.toLogString());
696
  }
697
  memcpy(key.qname, keyStr.c_str(), keyStr.size());
2✔
698
  key.qtype = qtype;
2✔
699

700
  {
2✔
701
    auto maps = d_maps.lock();
2✔
702
    auto& map = maps->d_qnames;
2✔
703
    int res = bpf_delete_elem(map.d_fd.getHandle(), &key);
2✔
704
    if (res == 0) {
2!
705
      --map.d_count;
2✔
706
    }
2✔
707
    else {
708
      throw std::runtime_error("Error removing qname address " + qname.toLogString() + ": " + stringerror());
709
    }
710
  }
2✔
711
}
2✔
712

713
std::vector<std::pair<ComboAddress, uint64_t>> BPFFilter::getAddrStats()
714
{
2✔
715
  std::vector<std::pair<ComboAddress, uint64_t>> result;
2✔
716
  {
2✔
717
    auto maps = d_maps.lock();
2✔
718
    result.reserve(maps->d_v4.d_count + maps->d_v6.d_count);
2✔
719
  }
2✔
720

721
  sockaddr_in v4Addr{};
2✔
722
  memset(&v4Addr, 0, sizeof(v4Addr));
2✔
723
  v4Addr.sin_family = AF_INET;
2✔
724

725
  uint32_t v4Key = 0;
2✔
726
  uint32_t nextV4Key{};
2✔
727
  CounterAndActionValue value{};
2✔
728

729
  std::array<uint8_t, 16> v6Key{};
2✔
730
  std::array<uint8_t, 16> nextV6Key{};
2✔
731
  sockaddr_in6 v6Addr{};
2✔
732
  memset(&v6Addr, 0, sizeof(v6Addr));
2✔
733
  v6Addr.sin6_family = AF_INET6;
2✔
734

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

738
  auto maps = d_maps.lock();
2✔
739

740
  {
2✔
741
    auto& map = maps->d_v4;
2✔
742
    int res = bpf_get_next_key(map.d_fd.getHandle(), &v4Key, &nextV4Key);
2✔
743

744
    while (res == 0) {
2!
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
  }
2✔
754

755
  {
2✔
756
    auto& map = maps->d_v6;
2✔
757
    int res = bpf_get_next_key(map.d_fd.getHandle(), v6Key.data(), nextV6Key.data());
2✔
758

759
    while (res == 0) {
4✔
760
      if (bpf_lookup_elem(map.d_fd.getHandle(), nextV6Key.data(), &value) == 0) {
2!
761
        memcpy(&v6Addr.sin6_addr.s6_addr, nextV6Key.data(), nextV6Key.size());
2✔
762

763
        result.emplace_back(ComboAddress(&v6Addr), value.counter);
2✔
764
      }
2✔
765

766
      res = bpf_get_next_key(map.d_fd.getHandle(), nextV6Key.data(), nextV6Key.data());
2✔
767
    }
2✔
768
  }
2✔
769

770
  return result;
2✔
771
}
2✔
772

773
std::vector<std::pair<Netmask, CounterAndActionValue>> BPFFilter::getRangeRule()
774
{
2✔
775
  CIDR4 cidr4[2];
2✔
776
  CIDR6 cidr6[2];
2✔
777
  std::vector<std::pair<Netmask, CounterAndActionValue>> result;
2✔
778

779
  sockaddr_in v4Addr;
2✔
780
  sockaddr_in6 v6Addr;
2✔
781
  CounterAndActionValue value;
2✔
782

783
  memset(cidr4, 0, sizeof(cidr4));
2✔
784
  memset(cidr6, 0, sizeof(cidr6));
2✔
785
  memset(&v4Addr, 0, sizeof(v4Addr));
2✔
786
  memset(&v6Addr, 0, sizeof(v6Addr));
2✔
787
  v4Addr.sin_family = AF_INET;
2✔
788
  v6Addr.sin6_family = AF_INET6;
2✔
789
  auto maps = d_maps.lock();
2✔
790
  result.reserve(maps->d_cidr4.d_count + maps->d_cidr6.d_count);
2✔
791
  {
2✔
792
    auto& map = maps->d_cidr4;
2✔
793
    int res = bpf_get_next_key(map.d_fd.getHandle(), &cidr4[0], &cidr4[1]);
2✔
794
    while (res == 0) {
2!
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
  }
2✔
803

804
  {
2✔
805
    auto& map = maps->d_cidr6;
2✔
806
    int res = bpf_get_next_key(map.d_fd.getHandle(), &cidr6[0], &cidr6[1]);
2✔
807
    while (res == 0) {
2!
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
  }
2✔
816
  return result;
2✔
817
}
2✔
818

819
std::vector<std::tuple<DNSName, uint16_t, uint64_t>> BPFFilter::getQNameStats()
820
{
2✔
821
  std::vector<std::tuple<DNSName, uint16_t, uint64_t>> result;
2✔
822

823
  if (d_mapFormat == MapFormat::Legacy) {
2!
824
    QNameKey key = {{0}};
2✔
825
    QNameKey nextKey = {{0}};
2✔
826
    QNameValue value;
2✔
827

828
    auto maps = d_maps.lock();
2✔
829
    auto& map = maps->d_qnames;
2✔
830
    result.reserve(map.d_count);
2✔
831
    int res = bpf_get_next_key(map.d_fd.getHandle(), &key, &nextKey);
2✔
832

833
    while (res == 0) {
4✔
834
      if (bpf_lookup_elem(map.d_fd.getHandle(), &nextKey, &value) == 0) {
2!
835
        nextKey.qname[sizeof(nextKey.qname) - 1] = '\0';
2✔
836
        result.emplace_back(DNSName(reinterpret_cast<const char*>(nextKey.qname), sizeof(nextKey.qname), 0, false), value.qtype, value.counter);
2✔
837
      }
2✔
838

839
      res = bpf_get_next_key(map.d_fd.getHandle(), &nextKey, &nextKey);
2✔
840
    }
2✔
841
  }
2✔
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;
2✔
865
}
2✔
866

867
uint64_t BPFFilter::getHits(const ComboAddress& requestor)
868
{
4✔
869
  CounterAndActionValue counter;
4✔
870

871
  if (requestor.isIPv4()) {
4!
872
    uint32_t key = htonl(requestor.sin4.sin_addr.s_addr);
4✔
873

874
    auto maps = d_maps.lock();
4✔
875
    auto& map = maps->d_v4;
4✔
876
    int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter);
4✔
877
    if (res == 0) {
4!
878
      return counter.counter;
4✔
879
    }
4✔
880
  }
4✔
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
}
4✔
898

899
#else
900

901
BPFFilter::BPFFilter(std::unordered_map<std::string, MapConfiguration>& configs, BPFFilter::MapFormat format, bool external)
902
{
903
  (void)configs;
904
  (void)format;
905
  (void)external;
906
}
907

908
void BPFFilter::addSocket(int)
909
{
910
  throw std::runtime_error("eBPF support not enabled");
911
}
912

913
void BPFFilter::removeSocket(int)
914
{
915
  throw std::runtime_error("eBPF support not enabled");
916
}
917

918
void BPFFilter::block(const ComboAddress&, BPFFilter::MatchAction)
919
{
920
  throw std::runtime_error("eBPF support not enabled");
921
}
922

923
void BPFFilter::unblock(const ComboAddress&)
924
{
925
  throw std::runtime_error("eBPF support not enabled");
926
}
927

928
void BPFFilter::block(const DNSName&, BPFFilter::MatchAction, uint16_t)
929
{
930
  throw std::runtime_error("eBPF support not enabled");
931
}
932

933
void BPFFilter::unblock(const DNSName&, uint16_t)
934
{
935
  throw std::runtime_error("eBPF support not enabled");
936
}
937

938
void BPFFilter::addRangeRule(const Netmask&, bool, BPFFilter::MatchAction)
939
{
940
  throw std::runtime_error("eBPF support not enabled");
941
}
942
void BPFFilter::rmRangeRule(const Netmask&)
943
{
944
  throw std::runtime_error("eBPF support not enabled");
945
}
946

947
std::vector<std::pair<Netmask, CounterAndActionValue>> BPFFilter::getRangeRule()
948
{
949
  std::vector<std::pair<Netmask, CounterAndActionValue>> result;
950
  return result;
951
}
952
std::vector<std::pair<ComboAddress, uint64_t>> BPFFilter::getAddrStats()
953
{
954
  std::vector<std::pair<ComboAddress, uint64_t>> result;
955
  return result;
956
}
957

958
std::vector<std::tuple<DNSName, uint16_t, uint64_t>> BPFFilter::getQNameStats()
959
{
960
  std::vector<std::tuple<DNSName, uint16_t, uint64_t>> result;
961
  return result;
962
}
963

964
uint64_t BPFFilter::getHits(const ComboAddress&)
965
{
966
  return 0;
967
}
968
#endif /* HAVE_EBPF */
969

970
bool BPFFilter::supportsMatchAction(MatchAction action) const
971
{
2✔
972
#ifdef HAVE_EBPF
2✔
973
  if (action == BPFFilter::MatchAction::Drop) {
2!
974
    return true;
2✔
975
  }
2✔
976
  return d_mapFormat == BPFFilter::MapFormat::WithActions;
977
#endif /* HAVE_EBPF */
NEW
978
  (void)action;
×
UNCOV
979
  return false;
×
980
}
2✔
981

982
bool BPFFilter::isExternal() const
983
{
6✔
984
#ifdef HAVE_EBPF
6✔
985
  return d_external;
6✔
986
#endif /* HAVE_EBPF */
987
  return false;
×
988
}
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