]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/bpf-filter.cc
Merge pull request #7628 from tcely/patch-3
[thirdparty/pdns.git] / pdns / bpf-filter.cc
CommitLineData
12471842
PL
1/*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
87b515ed
RG
22#include "bpf-filter.hh"
23
24#ifdef HAVE_EBPF
25
26#include <sys/syscall.h>
27#include <linux/bpf.h>
28
29#include "ext/libbpf/libbpf.h"
30
31static __u64 ptr_to_u64(void *ptr)
32{
33 return (__u64) (unsigned long) ptr;
34}
35
36int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size,
37 int max_entries)
38{
eace2c24
RG
39 union bpf_attr attr;
40 memset(&attr, 0, sizeof(attr));
87b515ed
RG
41 attr.map_type = map_type;
42 attr.key_size = key_size;
43 attr.value_size = value_size;
44 attr.max_entries = max_entries;
45 return syscall(SYS_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
46}
47
48int bpf_update_elem(int fd, void *key, void *value, unsigned long long flags)
49{
eace2c24
RG
50 union bpf_attr attr;
51 memset(&attr, 0, sizeof(attr));
87b515ed
RG
52 attr.map_fd = fd;
53 attr.key = ptr_to_u64(key);
54 attr.value = ptr_to_u64(value);
55 attr.flags = flags;
56 return syscall(SYS_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
57}
58
59int bpf_lookup_elem(int fd, void *key, void *value)
60{
eace2c24
RG
61 union bpf_attr attr;
62 memset(&attr, 0, sizeof(attr));
87b515ed
RG
63 attr.map_fd = fd;
64 attr.key = ptr_to_u64(key);
65 attr.value = ptr_to_u64(value);
66 return syscall(SYS_bpf, BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
67}
68
69int bpf_delete_elem(int fd, void *key)
70{
eace2c24
RG
71 union bpf_attr attr;
72 memset(&attr, 0, sizeof(attr));
87b515ed
RG
73 attr.map_fd = fd;
74 attr.key = ptr_to_u64(key);
75 return syscall(SYS_bpf, BPF_MAP_DELETE_ELEM, &attr, sizeof(attr));
76}
77
78int bpf_get_next_key(int fd, void *key, void *next_key)
79{
eace2c24
RG
80 union bpf_attr attr;
81 memset(&attr, 0, sizeof(attr));
87b515ed
RG
82 attr.map_fd = fd;
83 attr.key = ptr_to_u64(key);
84 attr.next_key = ptr_to_u64(next_key);
85 return syscall(SYS_bpf, BPF_MAP_GET_NEXT_KEY, &attr, sizeof(attr));
86}
87
88int bpf_prog_load(enum bpf_prog_type prog_type,
89 const struct bpf_insn *insns, int prog_len,
90 const char *license, int kern_version)
91{
92 char log_buf[65535];
eace2c24
RG
93 union bpf_attr attr;
94 memset(&attr, 0, sizeof(attr));
87b515ed
RG
95 attr.prog_type = prog_type;
96 attr.insns = ptr_to_u64((void *) insns);
97 attr.insn_cnt = prog_len / sizeof(struct bpf_insn);
98 attr.license = ptr_to_u64((void *) license);
99 attr.log_buf = ptr_to_u64(log_buf);
100 attr.log_size = sizeof(log_buf);
101 attr.log_level = 1;
102 /* assign one field outside of struct init to make sure any
103 * padding is zero initialized
104 */
105 attr.kern_version = kern_version;
106
107 long res = syscall(SYS_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
108 if (res == -1) {
109 if (errno == ENOSPC) {
110 /* not enough space in the log buffer */
111 attr.log_level = 0;
112 attr.log_size = 0;
113 attr.log_buf = ptr_to_u64(nullptr);
114 res = syscall(SYS_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
115 if (res != -1) {
116 return res;
117 }
118 }
119 throw std::runtime_error("Error loading BPF program: (" + std::string(strerror(errno)) + "):\n" + std::string(log_buf));
120 }
121 return res;
122}
123
124struct KeyV6
125{
126 uint8_t src[16];
127};
128
129struct QNameKey
130{
131 uint8_t qname[255];
132};
133
134struct QNameValue
135{
136 uint64_t counter;
137 uint16_t qtype;
138};
139
140BPFFilter::BPFFilter(uint32_t maxV4Addresses, uint32_t maxV6Addresses, uint32_t maxQNames): d_maxV4(maxV4Addresses), d_maxV6(maxV6Addresses), d_maxQNames(maxQNames)
141{
d45189b7
RG
142 d_v4map.fd = bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint64_t), (int) maxV4Addresses);
143 if (d_v4map.fd == -1) {
87b515ed
RG
144 throw std::runtime_error("Error creating a BPF v4 map of size " + std::to_string(maxV4Addresses) + ": " + std::string(strerror(errno)));
145 }
146
d45189b7
RG
147 d_v6map.fd = bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(struct KeyV6), sizeof(uint64_t), (int) maxV6Addresses);
148 if (d_v6map.fd == -1) {
87b515ed
RG
149 throw std::runtime_error("Error creating a BPF v6 map of size " + std::to_string(maxV6Addresses) + ": " + std::string(strerror(errno)));
150 }
151
d45189b7
RG
152 d_qnamemap.fd = bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(struct QNameKey), sizeof(struct QNameValue), (int) maxQNames);
153 if (d_qnamemap.fd == -1) {
87b515ed
RG
154 throw std::runtime_error("Error creating a BPF qname map of size " + std::to_string(maxQNames) + ": " + std::string(strerror(errno)));
155 }
87b515ed 156
d45189b7
RG
157 d_filtermap.fd = bpf_create_map(BPF_MAP_TYPE_PROG_ARRAY, sizeof(uint32_t), sizeof(uint32_t), 1);
158 if (d_filtermap.fd == -1) {
159 throw std::runtime_error("Error creating a BPF program map of size 1: " + std::string(strerror(errno)));
87b515ed 160 }
87b515ed 161
d45189b7
RG
162 struct bpf_insn main_filter[] = {
163#include "bpf-filter.main.ebpf"
164 };
165
166 d_mainfilter.fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER,
167 main_filter,
168 sizeof(main_filter),
169 "GPL",
170 0);
171 if (d_mainfilter.fd == -1) {
172 throw std::runtime_error("Error loading BPF main filter: " + std::string(strerror(errno)));
87b515ed 173 }
d45189b7
RG
174
175 struct bpf_insn qname_filter[] = {
176#include "bpf-filter.qname.ebpf"
177 };
178
179 d_qnamefilter.fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER,
180 qname_filter,
181 sizeof(qname_filter),
182 "GPL",
183 0);
184 if (d_qnamefilter.fd == -1) {
185 throw std::runtime_error("Error loading BPF qname filter: " + std::string(strerror(errno)));
87b515ed 186 }
d45189b7
RG
187
188 uint32_t key = 0;
189 int res = bpf_update_elem(d_filtermap.fd, &key, &d_qnamefilter.fd, BPF_ANY);
190 if (res != 0) {
191 throw std::runtime_error("Error updating BPF filters map: " + std::string(strerror(errno)));
87b515ed
RG
192 }
193}
194
195void BPFFilter::addSocket(int sock)
196{
d45189b7 197 int res = setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &d_mainfilter.fd, sizeof(d_mainfilter.fd));
87b515ed
RG
198
199 if (res != 0) {
200 throw std::runtime_error("Error attaching BPF filter to this socket: " + std::string(strerror(errno)));
201 }
202}
203
8429ad04
RG
204void BPFFilter::removeSocket(int sock)
205{
206 int res = setsockopt(sock, SOL_SOCKET, SO_DETACH_BPF, &d_mainfilter.fd, sizeof(d_mainfilter.fd));
207
208 if (res != 0) {
209 throw std::runtime_error("Error detaching BPF filter from this socket: " + std::string(strerror(errno)));
210 }
211}
212
87b515ed
RG
213void BPFFilter::block(const ComboAddress& addr)
214{
215 std::unique_lock<std::mutex> lock(d_mutex);
216
217 uint64_t counter = 0;
218 int res = 0;
219 if (addr.sin4.sin_family == AF_INET) {
220 uint32_t key = htonl(addr.sin4.sin_addr.s_addr);
221 if (d_v4Count >= d_maxV4) {
222 throw std::runtime_error("Table full when trying to block " + addr.toString());
223 }
224
d45189b7 225 res = bpf_lookup_elem(d_v4map.fd, &key, &counter);
87b515ed
RG
226 if (res != -1) {
227 throw std::runtime_error("Trying to block an already blocked address: " + addr.toString());
228 }
229
d45189b7 230 res = bpf_update_elem(d_v4map.fd, &key, &counter, BPF_NOEXIST);
87b515ed
RG
231 if (res == 0) {
232 d_v4Count++;
233 }
234 }
235 else if (addr.sin4.sin_family == AF_INET6) {
236 uint8_t key[16];
237 static_assert(sizeof(addr.sin6.sin6_addr.s6_addr) == sizeof(key), "POSIX mandates s6_addr to be an array of 16 uint8_t");
238 for (size_t idx = 0; idx < sizeof(key); idx++) {
239 key[idx] = addr.sin6.sin6_addr.s6_addr[idx];
240 }
241
242 if (d_v6Count >= d_maxV6) {
243 throw std::runtime_error("Table full when trying to block " + addr.toString());
244 }
245
d45189b7 246 res = bpf_lookup_elem(d_v6map.fd, &key, &counter);
87b515ed
RG
247 if (res != -1) {
248 throw std::runtime_error("Trying to block an already blocked address: " + addr.toString());
249 }
250
d45189b7 251 res = bpf_update_elem(d_v6map.fd, key, &counter, BPF_NOEXIST);
87b515ed
RG
252 if (res == 0) {
253 d_v6Count++;
254 }
255 }
256
257 if (res != 0) {
258 throw std::runtime_error("Error adding blocked address " + addr.toString() + ": " + std::string(strerror(errno)));
259 }
260}
261
262void BPFFilter::unblock(const ComboAddress& addr)
263{
264 std::unique_lock<std::mutex> lock(d_mutex);
265
266 int res = 0;
267 if (addr.sin4.sin_family == AF_INET) {
268 uint32_t key = htonl(addr.sin4.sin_addr.s_addr);
d45189b7 269 res = bpf_delete_elem(d_v4map.fd, &key);
87b515ed
RG
270 if (res == 0) {
271 d_v4Count--;
272 }
273 }
274 else if (addr.sin4.sin_family == AF_INET6) {
275 uint8_t key[16];
276 static_assert(sizeof(addr.sin6.sin6_addr.s6_addr) == sizeof(key), "POSIX mandates s6_addr to be an array of 16 uint8_t");
277 for (size_t idx = 0; idx < sizeof(key); idx++) {
278 key[idx] = addr.sin6.sin6_addr.s6_addr[idx];
279 }
280
d45189b7 281 res = bpf_delete_elem(d_v6map.fd, key);
87b515ed
RG
282 if (res == 0) {
283 d_v6Count--;
284 }
285 }
286
287 if (res != 0) {
288 throw std::runtime_error("Error removing blocked address " + addr.toString() + ": " + std::string(strerror(errno)));
289 }
290}
291
292void BPFFilter::block(const DNSName& qname, uint16_t qtype)
293{
294 struct QNameKey key;
295 struct QNameValue value;
296 memset(&key, 0, sizeof(key));
297 memset(&value, 0, sizeof(value));
298 value.counter = 0;
299 value.qtype = qtype;
300
d45189b7 301 std::string keyStr = qname.toDNSStringLC();
87b515ed 302 if (keyStr.size() > sizeof(key.qname)) {
86f1af1c 303 throw std::runtime_error("Invalid QName to block " + qname.toLogString());
87b515ed
RG
304 }
305 memcpy(key.qname, keyStr.c_str(), keyStr.size());
306
307 {
308 std::unique_lock<std::mutex> lock(d_mutex);
309 if (d_qNamesCount >= d_maxQNames) {
86f1af1c 310 throw std::runtime_error("Table full when trying to block " + qname.toLogString());
87b515ed
RG
311 }
312
d45189b7 313 int res = bpf_lookup_elem(d_qnamemap.fd, &key, &value);
87b515ed 314 if (res != -1) {
86f1af1c 315 throw std::runtime_error("Trying to block an already blocked qname: " + qname.toLogString());
87b515ed
RG
316 }
317
d45189b7 318 res = bpf_update_elem(d_qnamemap.fd, &key, &value, BPF_NOEXIST);
87b515ed
RG
319 if (res == 0) {
320 d_qNamesCount++;
321 }
322
323 if (res != 0) {
86f1af1c 324 throw std::runtime_error("Error adding blocked qname " + qname.toLogString() + ": " + std::string(strerror(errno)));
87b515ed
RG
325 }
326 }
327}
328
329void BPFFilter::unblock(const DNSName& qname, uint16_t qtype)
330{
64267f0a 331 struct QNameKey key = { { 0 } };
d45189b7 332 std::string keyStr = qname.toDNSStringLC();
87b515ed
RG
333 (void) qtype;
334
335 if (keyStr.size() > sizeof(key.qname)) {
86f1af1c 336 throw std::runtime_error("Invalid QName to block " + qname.toLogString());
87b515ed
RG
337 }
338 memcpy(key.qname, keyStr.c_str(), keyStr.size());
339
340 {
341 std::unique_lock<std::mutex> lock(d_mutex);
342
d45189b7 343 int res = bpf_delete_elem(d_qnamemap.fd, &key);
87b515ed
RG
344 if (res == 0) {
345 d_qNamesCount--;
346 }
347 else {
86f1af1c 348 throw std::runtime_error("Error removing qname address " + qname.toLogString() + ": " + std::string(strerror(errno)));
87b515ed
RG
349 }
350 }
351}
352
353std::vector<std::pair<ComboAddress, uint64_t> > BPFFilter::getAddrStats()
354{
355 std::vector<std::pair<ComboAddress, uint64_t> > result;
356 std::unique_lock<std::mutex> lock(d_mutex);
357
358 uint32_t v4Key = 0;
359 uint32_t nextV4Key;
360 uint64_t value;
d45189b7 361 int res = bpf_get_next_key(d_v4map.fd, &v4Key, &nextV4Key);
eace2c24
RG
362 sockaddr_in v4Addr;
363 memset(&v4Addr, 0, sizeof(v4Addr));
87b515ed
RG
364 v4Addr.sin_family = AF_INET;
365
366 while (res == 0) {
367 v4Key = nextV4Key;
d45189b7 368 if (bpf_lookup_elem(d_v4map.fd, &v4Key, &value) == 0) {
87b515ed
RG
369 v4Addr.sin_addr.s_addr = ntohl(v4Key);
370 result.push_back(make_pair(ComboAddress(&v4Addr), value));
371 }
372
d45189b7 373 res = bpf_get_next_key(d_v4map.fd, &v4Key, &nextV4Key);
87b515ed
RG
374 }
375
376 uint8_t v6Key[16];
377 uint8_t nextV6Key[16];
eace2c24
RG
378 sockaddr_in6 v6Addr;
379 memset(&v6Addr, 0, sizeof(v6Addr));
87b515ed 380 v6Addr.sin6_family = AF_INET6;
eace2c24 381
87b515ed
RG
382 static_assert(sizeof(v6Addr.sin6_addr.s6_addr) == sizeof(v6Key), "POSIX mandates s6_addr to be an array of 16 uint8_t");
383 for (size_t idx = 0; idx < sizeof(v6Key); idx++) {
384 v6Key[idx] = 0;
385 }
386
d45189b7 387 res = bpf_get_next_key(d_v6map.fd, &v6Key, &nextV6Key);
87b515ed
RG
388
389 while (res == 0) {
d45189b7 390 if (bpf_lookup_elem(d_v6map.fd, &nextV6Key, &value) == 0) {
87b515ed
RG
391 for (size_t idx = 0; idx < sizeof(nextV6Key); idx++) {
392 v6Addr.sin6_addr.s6_addr[idx] = nextV6Key[idx];
393 }
394 result.push_back(make_pair(ComboAddress(&v6Addr), value));
395 }
396
d45189b7 397 res = bpf_get_next_key(d_v6map.fd, &nextV6Key, &nextV6Key);
87b515ed
RG
398 }
399 return result;
400}
401
402std::vector<std::tuple<DNSName, uint16_t, uint64_t> > BPFFilter::getQNameStats()
403{
404 std::vector<std::tuple<DNSName, uint16_t, uint64_t> > result;
405 std::unique_lock<std::mutex> lock(d_mutex);
406
64267f0a
RG
407 struct QNameKey key = { { 0 } };
408 struct QNameKey nextKey = { { 0 } };
87b515ed
RG
409 struct QNameValue value;
410
d45189b7 411 int res = bpf_get_next_key(d_qnamemap.fd, &key, &nextKey);
87b515ed
RG
412
413 while (res == 0) {
d45189b7 414 if (bpf_lookup_elem(d_qnamemap.fd, &nextKey, &value) == 0) {
87b515ed
RG
415 nextKey.qname[sizeof(nextKey.qname) - 1 ] = '\0';
416 result.push_back(std::make_tuple(DNSName((const char*) nextKey.qname, sizeof(nextKey.qname), 0, false), value.qtype, value.counter));
417 }
418
d45189b7 419 res = bpf_get_next_key(d_qnamemap.fd, &nextKey, &nextKey);
87b515ed
RG
420 }
421 return result;
422}
423#endif /* HAVE_EBPF */