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