]>
Commit | Line | Data |
---|---|---|
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 | ||
16f0c288 OM |
31 | #include "misc.hh" |
32 | ||
87b515ed RG |
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 | { | |
eace2c24 RG |
41 | union bpf_attr attr; |
42 | memset(&attr, 0, sizeof(attr)); | |
87b515ed RG |
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 | { | |
eace2c24 RG |
52 | union bpf_attr attr; |
53 | memset(&attr, 0, sizeof(attr)); | |
87b515ed RG |
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 | { | |
eace2c24 RG |
63 | union bpf_attr attr; |
64 | memset(&attr, 0, sizeof(attr)); | |
87b515ed RG |
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 | { | |
eace2c24 RG |
73 | union bpf_attr attr; |
74 | memset(&attr, 0, sizeof(attr)); | |
87b515ed RG |
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 | { | |
eace2c24 RG |
82 | union bpf_attr attr; |
83 | memset(&attr, 0, sizeof(attr)); | |
87b515ed RG |
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]; | |
eace2c24 RG |
95 | union bpf_attr attr; |
96 | memset(&attr, 0, sizeof(attr)); | |
87b515ed RG |
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 | } | |
a2a81d42 | 121 | throw std::runtime_error("Error loading BPF program: (" + stringerror() + "):\n" + std::string(log_buf)); |
87b515ed RG |
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 | { | |
d45189b7 RG |
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) { | |
a2a81d42 | 146 | throw std::runtime_error("Error creating a BPF v4 map of size " + std::to_string(maxV4Addresses) + ": " + stringerror()); |
87b515ed RG |
147 | } |
148 | ||
d45189b7 RG |
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) { | |
a2a81d42 | 151 | throw std::runtime_error("Error creating a BPF v6 map of size " + std::to_string(maxV6Addresses) + ": " + stringerror()); |
87b515ed RG |
152 | } |
153 | ||
d45189b7 RG |
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) { | |
a2a81d42 | 156 | throw std::runtime_error("Error creating a BPF qname map of size " + std::to_string(maxQNames) + ": " + stringerror()); |
87b515ed | 157 | } |
87b515ed | 158 | |
d45189b7 RG |
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) { | |
a2a81d42 | 161 | throw std::runtime_error("Error creating a BPF program map of size 1: " + stringerror()); |
87b515ed | 162 | } |
87b515ed | 163 | |
d45189b7 RG |
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) { | |
a2a81d42 | 174 | throw std::runtime_error("Error loading BPF main filter: " + stringerror()); |
87b515ed | 175 | } |
d45189b7 RG |
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) { | |
a2a81d42 | 187 | throw std::runtime_error("Error loading BPF qname filter: " + stringerror()); |
87b515ed | 188 | } |
d45189b7 RG |
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) { | |
16f0c288 | 193 | throw std::runtime_error("Error updating BPF filters map: " + stringerror()); |
87b515ed RG |
194 | } |
195 | } | |
196 | ||
197 | void BPFFilter::addSocket(int sock) | |
198 | { | |
d45189b7 | 199 | int res = setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &d_mainfilter.fd, sizeof(d_mainfilter.fd)); |
87b515ed RG |
200 | |
201 | if (res != 0) { | |
a2a81d42 | 202 | throw std::runtime_error("Error attaching BPF filter to this socket: " + stringerror()); |
87b515ed RG |
203 | } |
204 | } | |
205 | ||
8429ad04 RG |
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) { | |
a2a81d42 | 211 | throw std::runtime_error("Error detaching BPF filter from this socket: " + stringerror()); |
8429ad04 RG |
212 | } |
213 | } | |
214 | ||
87b515ed RG |
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 | ||
d45189b7 | 227 | res = bpf_lookup_elem(d_v4map.fd, &key, &counter); |
87b515ed RG |
228 | if (res != -1) { |
229 | throw std::runtime_error("Trying to block an already blocked address: " + addr.toString()); | |
230 | } | |
231 | ||
d45189b7 | 232 | res = bpf_update_elem(d_v4map.fd, &key, &counter, BPF_NOEXIST); |
87b515ed RG |
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 | ||
d45189b7 | 248 | res = bpf_lookup_elem(d_v6map.fd, &key, &counter); |
87b515ed RG |
249 | if (res != -1) { |
250 | throw std::runtime_error("Trying to block an already blocked address: " + addr.toString()); | |
251 | } | |
252 | ||
d45189b7 | 253 | res = bpf_update_elem(d_v6map.fd, key, &counter, BPF_NOEXIST); |
87b515ed RG |
254 | if (res == 0) { |
255 | d_v6Count++; | |
256 | } | |
257 | } | |
258 | ||
259 | if (res != 0) { | |
a2a81d42 | 260 | throw std::runtime_error("Error adding blocked address " + addr.toString() + ": " + stringerror()); |
87b515ed RG |
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); | |
d45189b7 | 271 | res = bpf_delete_elem(d_v4map.fd, &key); |
87b515ed RG |
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 | ||
d45189b7 | 283 | res = bpf_delete_elem(d_v6map.fd, key); |
87b515ed RG |
284 | if (res == 0) { |
285 | d_v6Count--; | |
286 | } | |
287 | } | |
288 | ||
289 | if (res != 0) { | |
a2a81d42 | 290 | throw std::runtime_error("Error removing blocked address " + addr.toString() + ": " + stringerror()); |
87b515ed RG |
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 | ||
d45189b7 | 303 | std::string keyStr = qname.toDNSStringLC(); |
87b515ed | 304 | if (keyStr.size() > sizeof(key.qname)) { |
86f1af1c | 305 | throw std::runtime_error("Invalid QName to block " + qname.toLogString()); |
87b515ed RG |
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) { | |
86f1af1c | 312 | throw std::runtime_error("Table full when trying to block " + qname.toLogString()); |
87b515ed RG |
313 | } |
314 | ||
d45189b7 | 315 | int res = bpf_lookup_elem(d_qnamemap.fd, &key, &value); |
87b515ed | 316 | if (res != -1) { |
86f1af1c | 317 | throw std::runtime_error("Trying to block an already blocked qname: " + qname.toLogString()); |
87b515ed RG |
318 | } |
319 | ||
d45189b7 | 320 | res = bpf_update_elem(d_qnamemap.fd, &key, &value, BPF_NOEXIST); |
87b515ed RG |
321 | if (res == 0) { |
322 | d_qNamesCount++; | |
323 | } | |
324 | ||
325 | if (res != 0) { | |
16f0c288 | 326 | throw std::runtime_error("Error adding blocked qname " + qname.toLogString() + ": " + stringerror()); |
87b515ed RG |
327 | } |
328 | } | |
329 | } | |
330 | ||
331 | void BPFFilter::unblock(const DNSName& qname, uint16_t qtype) | |
332 | { | |
64267f0a | 333 | struct QNameKey key = { { 0 } }; |
d45189b7 | 334 | std::string keyStr = qname.toDNSStringLC(); |
87b515ed RG |
335 | (void) qtype; |
336 | ||
337 | if (keyStr.size() > sizeof(key.qname)) { | |
86f1af1c | 338 | throw std::runtime_error("Invalid QName to block " + qname.toLogString()); |
87b515ed RG |
339 | } |
340 | memcpy(key.qname, keyStr.c_str(), keyStr.size()); | |
341 | ||
342 | { | |
343 | std::unique_lock<std::mutex> lock(d_mutex); | |
344 | ||
d45189b7 | 345 | int res = bpf_delete_elem(d_qnamemap.fd, &key); |
87b515ed RG |
346 | if (res == 0) { |
347 | d_qNamesCount--; | |
348 | } | |
349 | else { | |
16f0c288 | 350 | throw std::runtime_error("Error removing qname address " + qname.toLogString() + ": " + stringerror()); |
87b515ed RG |
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; | |
d45189b7 | 363 | int res = bpf_get_next_key(d_v4map.fd, &v4Key, &nextV4Key); |
eace2c24 RG |
364 | sockaddr_in v4Addr; |
365 | memset(&v4Addr, 0, sizeof(v4Addr)); | |
87b515ed RG |
366 | v4Addr.sin_family = AF_INET; |
367 | ||
368 | while (res == 0) { | |
369 | v4Key = nextV4Key; | |
d45189b7 | 370 | if (bpf_lookup_elem(d_v4map.fd, &v4Key, &value) == 0) { |
87b515ed RG |
371 | v4Addr.sin_addr.s_addr = ntohl(v4Key); |
372 | result.push_back(make_pair(ComboAddress(&v4Addr), value)); | |
373 | } | |
374 | ||
d45189b7 | 375 | res = bpf_get_next_key(d_v4map.fd, &v4Key, &nextV4Key); |
87b515ed RG |
376 | } |
377 | ||
378 | uint8_t v6Key[16]; | |
379 | uint8_t nextV6Key[16]; | |
eace2c24 RG |
380 | sockaddr_in6 v6Addr; |
381 | memset(&v6Addr, 0, sizeof(v6Addr)); | |
87b515ed | 382 | v6Addr.sin6_family = AF_INET6; |
eace2c24 | 383 | |
87b515ed RG |
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 | ||
d45189b7 | 389 | res = bpf_get_next_key(d_v6map.fd, &v6Key, &nextV6Key); |
87b515ed RG |
390 | |
391 | while (res == 0) { | |
d45189b7 | 392 | if (bpf_lookup_elem(d_v6map.fd, &nextV6Key, &value) == 0) { |
87b515ed RG |
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 | ||
d45189b7 | 399 | res = bpf_get_next_key(d_v6map.fd, &nextV6Key, &nextV6Key); |
87b515ed RG |
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 | ||
64267f0a RG |
409 | struct QNameKey key = { { 0 } }; |
410 | struct QNameKey nextKey = { { 0 } }; | |
87b515ed RG |
411 | struct QNameValue value; |
412 | ||
d45189b7 | 413 | int res = bpf_get_next_key(d_qnamemap.fd, &key, &nextKey); |
87b515ed RG |
414 | |
415 | while (res == 0) { | |
d45189b7 | 416 | if (bpf_lookup_elem(d_qnamemap.fd, &nextKey, &value) == 0) { |
87b515ed RG |
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 | ||
d45189b7 | 421 | res = bpf_get_next_key(d_qnamemap.fd, &nextKey, &nextKey); |
87b515ed RG |
422 | } |
423 | return result; | |
424 | } | |
425 | #endif /* HAVE_EBPF */ |