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"
31 static __u64
ptr_to_u64(void *ptr
)
33 return (__u64
) (unsigned long) ptr
;
36 int bpf_create_map(enum bpf_map_type map_type
, int key_size
, int value_size
,
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
));
47 int bpf_update_elem(int fd
, void *key
, void *value
, unsigned long long flags
)
49 union bpf_attr attr
= { 0 };
51 attr
.key
= ptr_to_u64(key
);
52 attr
.value
= ptr_to_u64(value
);
54 return syscall(SYS_bpf
, BPF_MAP_UPDATE_ELEM
, &attr
, sizeof(attr
));
57 int bpf_lookup_elem(int fd
, void *key
, void *value
)
59 union bpf_attr attr
= { 0 };
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
));
66 int bpf_delete_elem(int fd
, void *key
)
68 union bpf_attr attr
= { 0 };
70 attr
.key
= ptr_to_u64(key
);
71 return syscall(SYS_bpf
, BPF_MAP_DELETE_ELEM
, &attr
, sizeof(attr
));
74 int bpf_get_next_key(int fd
, void *key
, void *next_key
)
76 union bpf_attr attr
= { 0 };
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
));
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
)
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
);
96 /* assign one field outside of struct init to make sure any
97 * padding is zero initialized
99 attr
.kern_version
= kern_version
;
101 long res
= syscall(SYS_bpf
, BPF_PROG_LOAD
, &attr
, sizeof(attr
));
103 if (errno
== ENOSPC
) {
104 /* not enough space in the log buffer */
107 attr
.log_buf
= ptr_to_u64(nullptr);
108 res
= syscall(SYS_bpf
, BPF_PROG_LOAD
, &attr
, sizeof(attr
));
113 throw std::runtime_error("Error loading BPF program: (" + std::string(strerror(errno
)) + "):\n" + std::string(log_buf
));
134 BPFFilter::BPFFilter(uint32_t maxV4Addresses
, uint32_t maxV6Addresses
, uint32_t maxQNames
): d_maxV4(maxV4Addresses
), d_maxV6(maxV6Addresses
), d_maxQNames(maxQNames
)
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
)));
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
)));
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
)));
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
)));
156 struct bpf_insn main_filter
[] = {
157 #include "bpf-filter.main.ebpf"
160 d_mainfilter
.fd
= bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER
,
165 if (d_mainfilter
.fd
== -1) {
166 throw std::runtime_error("Error loading BPF main filter: " + std::string(strerror(errno
)));
169 struct bpf_insn qname_filter
[] = {
170 #include "bpf-filter.qname.ebpf"
173 d_qnamefilter
.fd
= bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER
,
175 sizeof(qname_filter
),
178 if (d_qnamefilter
.fd
== -1) {
179 throw std::runtime_error("Error loading BPF qname filter: " + std::string(strerror(errno
)));
183 int res
= bpf_update_elem(d_filtermap
.fd
, &key
, &d_qnamefilter
.fd
, BPF_ANY
);
185 throw std::runtime_error("Error updating BPF filters map: " + std::string(strerror(errno
)));
189 void BPFFilter::addSocket(int sock
)
191 int res
= setsockopt(sock
, SOL_SOCKET
, SO_ATTACH_BPF
, &d_mainfilter
.fd
, sizeof(d_mainfilter
.fd
));
194 throw std::runtime_error("Error attaching BPF filter to this socket: " + std::string(strerror(errno
)));
198 void BPFFilter::removeSocket(int sock
)
200 int res
= setsockopt(sock
, SOL_SOCKET
, SO_DETACH_BPF
, &d_mainfilter
.fd
, sizeof(d_mainfilter
.fd
));
203 throw std::runtime_error("Error detaching BPF filter from this socket: " + std::string(strerror(errno
)));
207 void BPFFilter::block(const ComboAddress
& addr
)
209 std::unique_lock
<std::mutex
> lock(d_mutex
);
211 uint64_t counter
= 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());
219 res
= bpf_lookup_elem(d_v4map
.fd
, &key
, &counter
);
221 throw std::runtime_error("Trying to block an already blocked address: " + addr
.toString());
224 res
= bpf_update_elem(d_v4map
.fd
, &key
, &counter
, BPF_NOEXIST
);
229 else if (addr
.sin4
.sin_family
== AF_INET6
) {
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
];
236 if (d_v6Count
>= d_maxV6
) {
237 throw std::runtime_error("Table full when trying to block " + addr
.toString());
240 res
= bpf_lookup_elem(d_v6map
.fd
, &key
, &counter
);
242 throw std::runtime_error("Trying to block an already blocked address: " + addr
.toString());
245 res
= bpf_update_elem(d_v6map
.fd
, key
, &counter
, BPF_NOEXIST
);
252 throw std::runtime_error("Error adding blocked address " + addr
.toString() + ": " + std::string(strerror(errno
)));
256 void BPFFilter::unblock(const ComboAddress
& addr
)
258 std::unique_lock
<std::mutex
> lock(d_mutex
);
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
);
268 else if (addr
.sin4
.sin_family
== AF_INET6
) {
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
];
275 res
= bpf_delete_elem(d_v6map
.fd
, key
);
282 throw std::runtime_error("Error removing blocked address " + addr
.toString() + ": " + std::string(strerror(errno
)));
286 void BPFFilter::block(const DNSName
& qname
, uint16_t qtype
)
289 struct QNameValue value
;
290 memset(&key
, 0, sizeof(key
));
291 memset(&value
, 0, sizeof(value
));
295 std::string keyStr
= qname
.toDNSStringLC();
296 if (keyStr
.size() > sizeof(key
.qname
)) {
297 throw std::runtime_error("Invalid QName to block " + qname
.toLogString());
299 memcpy(key
.qname
, keyStr
.c_str(), keyStr
.size());
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());
307 int res
= bpf_lookup_elem(d_qnamemap
.fd
, &key
, &value
);
309 throw std::runtime_error("Trying to block an already blocked qname: " + qname
.toLogString());
312 res
= bpf_update_elem(d_qnamemap
.fd
, &key
, &value
, BPF_NOEXIST
);
318 throw std::runtime_error("Error adding blocked qname " + qname
.toLogString() + ": " + std::string(strerror(errno
)));
323 void BPFFilter::unblock(const DNSName
& qname
, uint16_t qtype
)
325 struct QNameKey key
= { { 0 } };
326 std::string keyStr
= qname
.toDNSStringLC();
329 if (keyStr
.size() > sizeof(key
.qname
)) {
330 throw std::runtime_error("Invalid QName to block " + qname
.toLogString());
332 memcpy(key
.qname
, keyStr
.c_str(), keyStr
.size());
335 std::unique_lock
<std::mutex
> lock(d_mutex
);
337 int res
= bpf_delete_elem(d_qnamemap
.fd
, &key
);
342 throw std::runtime_error("Error removing qname address " + qname
.toLogString() + ": " + std::string(strerror(errno
)));
347 std::vector
<std::pair
<ComboAddress
, uint64_t> > BPFFilter::getAddrStats()
349 std::vector
<std::pair
<ComboAddress
, uint64_t> > result
;
350 std::unique_lock
<std::mutex
> lock(d_mutex
);
355 int res
= bpf_get_next_key(d_v4map
.fd
, &v4Key
, &nextV4Key
);
356 sockaddr_in v4Addr
= { 0 };
358 v4Addr
.sin_family
= AF_INET
;
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
));
367 res
= bpf_get_next_key(d_v4map
.fd
, &v4Key
, &nextV4Key
);
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
++) {
380 res
= bpf_get_next_key(d_v6map
.fd
, &v6Key
, &nextV6Key
);
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
];
387 result
.push_back(make_pair(ComboAddress(&v6Addr
), value
));
390 res
= bpf_get_next_key(d_v6map
.fd
, &nextV6Key
, &nextV6Key
);
395 std::vector
<std::tuple
<DNSName
, uint16_t, uint64_t> > BPFFilter::getQNameStats()
397 std::vector
<std::tuple
<DNSName
, uint16_t, uint64_t> > result
;
398 std::unique_lock
<std::mutex
> lock(d_mutex
);
400 struct QNameKey key
= { { 0 } };
401 struct QNameKey nextKey
= { { 0 } };
402 struct QNameValue value
;
404 int res
= bpf_get_next_key(d_qnamemap
.fd
, &key
, &nextKey
);
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
));
412 res
= bpf_get_next_key(d_qnamemap
.fd
, &nextKey
, &nextKey
);
416 #endif /* HAVE_EBPF */