]>
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 | ||
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 | { | |
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 | ||
48 | int 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 | ||
59 | int 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 | ||
69 | int 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 | ||
78 | int 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 | ||
88 | int 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 | ||
124 | struct KeyV6 | |
125 | { | |
126 | uint8_t src[16]; | |
127 | }; | |
128 | ||
129 | struct QNameKey | |
130 | { | |
131 | uint8_t qname[255]; | |
132 | }; | |
133 | ||
134 | struct QNameValue | |
135 | { | |
136 | uint64_t counter; | |
137 | uint16_t qtype; | |
138 | }; | |
139 | ||
140 | BPFFilter::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 | ||
195 | void 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 |
204 | void 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 |
213 | void 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 | ||
262 | void 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 | ||
292 | void 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 | ||
329 | void 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 | ||
353 | std::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 | ||
402 | std::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 */ |