2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
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.
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.
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.
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.
22 #include "bpf-filter.hh"
26 #include <sys/syscall.h>
27 #include <linux/bpf.h>
29 #include "ext/libbpf/libbpf.h"
33 static __u64
ptr_to_u64(void *ptr
)
35 return (__u64
) (unsigned long) ptr
;
38 int bpf_create_map(enum bpf_map_type map_type
, int key_size
, int value_size
,
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
));
50 int bpf_update_elem(int fd
, void *key
, void *value
, unsigned long long flags
)
53 memset(&attr
, 0, sizeof(attr
));
55 attr
.key
= ptr_to_u64(key
);
56 attr
.value
= ptr_to_u64(value
);
58 return syscall(SYS_bpf
, BPF_MAP_UPDATE_ELEM
, &attr
, sizeof(attr
));
61 int bpf_lookup_elem(int fd
, void *key
, void *value
)
64 memset(&attr
, 0, sizeof(attr
));
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
));
71 int bpf_delete_elem(int fd
, void *key
)
74 memset(&attr
, 0, sizeof(attr
));
76 attr
.key
= ptr_to_u64(key
);
77 return syscall(SYS_bpf
, BPF_MAP_DELETE_ELEM
, &attr
, sizeof(attr
));
80 int bpf_get_next_key(int fd
, void *key
, void *next_key
)
83 memset(&attr
, 0, sizeof(attr
));
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
));
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
)
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
);
104 /* assign one field outside of struct init to make sure any
105 * padding is zero initialized
107 attr
.kern_version
= kern_version
;
109 long res
= syscall(SYS_bpf
, BPF_PROG_LOAD
, &attr
, sizeof(attr
));
111 if (errno
== ENOSPC
) {
112 /* not enough space in the log buffer */
115 attr
.log_buf
= ptr_to_u64(nullptr);
116 res
= syscall(SYS_bpf
, BPF_PROG_LOAD
, &attr
, sizeof(attr
));
121 throw std::runtime_error("Error loading BPF program: (" + stringerror() + "):\n" + std::string(log_buf
));
142 BPFFilter::BPFFilter(uint32_t maxV4Addresses
, uint32_t maxV6Addresses
, uint32_t maxQNames
): d_maxV4(maxV4Addresses
), d_maxV6(maxV6Addresses
), d_maxQNames(maxQNames
)
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());
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());
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());
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());
164 struct bpf_insn main_filter
[] = {
165 #include "bpf-filter.main.ebpf"
168 d_mainfilter
.fd
= bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER
,
173 if (d_mainfilter
.fd
== -1) {
174 throw std::runtime_error("Error loading BPF main filter: " + stringerror());
177 struct bpf_insn qname_filter
[] = {
178 #include "bpf-filter.qname.ebpf"
181 d_qnamefilter
.fd
= bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER
,
183 sizeof(qname_filter
),
186 if (d_qnamefilter
.fd
== -1) {
187 throw std::runtime_error("Error loading BPF qname filter: " + stringerror());
191 int res
= bpf_update_elem(d_filtermap
.fd
, &key
, &d_qnamefilter
.fd
, BPF_ANY
);
193 throw std::runtime_error("Error updating BPF filters map: " + stringerror());
197 void BPFFilter::addSocket(int sock
)
199 int res
= setsockopt(sock
, SOL_SOCKET
, SO_ATTACH_BPF
, &d_mainfilter
.fd
, sizeof(d_mainfilter
.fd
));
202 throw std::runtime_error("Error attaching BPF filter to this socket: " + stringerror());
206 void BPFFilter::removeSocket(int sock
)
208 int res
= setsockopt(sock
, SOL_SOCKET
, SO_DETACH_BPF
, &d_mainfilter
.fd
, sizeof(d_mainfilter
.fd
));
211 throw std::runtime_error("Error detaching BPF filter from this socket: " + stringerror());
215 void BPFFilter::block(const ComboAddress
& addr
)
217 std::unique_lock
<std::mutex
> lock(d_mutex
);
219 uint64_t counter
= 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());
227 res
= bpf_lookup_elem(d_v4map
.fd
, &key
, &counter
);
229 throw std::runtime_error("Trying to block an already blocked address: " + addr
.toString());
232 res
= bpf_update_elem(d_v4map
.fd
, &key
, &counter
, BPF_NOEXIST
);
237 else if (addr
.sin4
.sin_family
== AF_INET6
) {
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
];
244 if (d_v6Count
>= d_maxV6
) {
245 throw std::runtime_error("Table full when trying to block " + addr
.toString());
248 res
= bpf_lookup_elem(d_v6map
.fd
, &key
, &counter
);
250 throw std::runtime_error("Trying to block an already blocked address: " + addr
.toString());
253 res
= bpf_update_elem(d_v6map
.fd
, key
, &counter
, BPF_NOEXIST
);
260 throw std::runtime_error("Error adding blocked address " + addr
.toString() + ": " + stringerror());
264 void BPFFilter::unblock(const ComboAddress
& addr
)
266 std::unique_lock
<std::mutex
> lock(d_mutex
);
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
);
276 else if (addr
.sin4
.sin_family
== AF_INET6
) {
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
];
283 res
= bpf_delete_elem(d_v6map
.fd
, key
);
290 throw std::runtime_error("Error removing blocked address " + addr
.toString() + ": " + stringerror());
294 void BPFFilter::block(const DNSName
& qname
, uint16_t qtype
)
297 struct QNameValue value
;
298 memset(&key
, 0, sizeof(key
));
299 memset(&value
, 0, sizeof(value
));
303 std::string keyStr
= qname
.toDNSStringLC();
304 if (keyStr
.size() > sizeof(key
.qname
)) {
305 throw std::runtime_error("Invalid QName to block " + qname
.toLogString());
307 memcpy(key
.qname
, keyStr
.c_str(), keyStr
.size());
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());
315 int res
= bpf_lookup_elem(d_qnamemap
.fd
, &key
, &value
);
317 throw std::runtime_error("Trying to block an already blocked qname: " + qname
.toLogString());
320 res
= bpf_update_elem(d_qnamemap
.fd
, &key
, &value
, BPF_NOEXIST
);
326 throw std::runtime_error("Error adding blocked qname " + qname
.toLogString() + ": " + stringerror());
331 void BPFFilter::unblock(const DNSName
& qname
, uint16_t qtype
)
333 struct QNameKey key
= { { 0 } };
334 std::string keyStr
= qname
.toDNSStringLC();
337 if (keyStr
.size() > sizeof(key
.qname
)) {
338 throw std::runtime_error("Invalid QName to block " + qname
.toLogString());
340 memcpy(key
.qname
, keyStr
.c_str(), keyStr
.size());
343 std::unique_lock
<std::mutex
> lock(d_mutex
);
345 int res
= bpf_delete_elem(d_qnamemap
.fd
, &key
);
350 throw std::runtime_error("Error removing qname address " + qname
.toLogString() + ": " + stringerror());
355 std::vector
<std::pair
<ComboAddress
, uint64_t> > BPFFilter::getAddrStats()
357 std::vector
<std::pair
<ComboAddress
, uint64_t> > result
;
358 std::unique_lock
<std::mutex
> lock(d_mutex
);
363 int res
= bpf_get_next_key(d_v4map
.fd
, &v4Key
, &nextV4Key
);
365 memset(&v4Addr
, 0, sizeof(v4Addr
));
366 v4Addr
.sin_family
= AF_INET
;
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
));
375 res
= bpf_get_next_key(d_v4map
.fd
, &v4Key
, &nextV4Key
);
379 uint8_t nextV6Key
[16];
381 memset(&v6Addr
, 0, sizeof(v6Addr
));
382 v6Addr
.sin6_family
= AF_INET6
;
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
++) {
389 res
= bpf_get_next_key(d_v6map
.fd
, &v6Key
, &nextV6Key
);
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
];
396 result
.push_back(make_pair(ComboAddress(&v6Addr
), value
));
399 res
= bpf_get_next_key(d_v6map
.fd
, &nextV6Key
, &nextV6Key
);
404 std::vector
<std::tuple
<DNSName
, uint16_t, uint64_t> > BPFFilter::getQNameStats()
406 std::vector
<std::tuple
<DNSName
, uint16_t, uint64_t> > result
;
407 std::unique_lock
<std::mutex
> lock(d_mutex
);
409 struct QNameKey key
= { { 0 } };
410 struct QNameKey nextKey
= { { 0 } };
411 struct QNameValue value
;
413 int res
= bpf_get_next_key(d_qnamemap
.fd
, &key
, &nextKey
);
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
));
421 res
= bpf_get_next_key(d_qnamemap
.fd
, &nextKey
, &nextKey
);
425 #endif /* HAVE_EBPF */