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