From: Remi Gacogne Date: Fri, 21 Feb 2025 13:38:55 +0000 (+0100) Subject: dnsdist: Use 65535 instead of 255 to block all types via eBPF X-Git-Tag: dnsdist-2.0.0-alpha1~55^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=refs%2Fpull%2F15199%2Fhead;p=thirdparty%2Fpdns.git dnsdist: Use 65535 instead of 255 to block all types via eBPF Our eBPF filtering code used to treat the `255` (`ANY`) qtype as a special value meaning to block queries for all types. Unfortunately it prevented blocking queries for the `ANY` type only, which is a legitimate use-case. After this commit `255` is no longer a special value, and `65535` (a reserved value) is used instead to represent all types. This is a breaking change that should NOT be backported. --- diff --git a/contrib/xdp-filter.ebpf.src b/contrib/xdp-filter.ebpf.src index f25ba2c08e..31275d2ff7 100644 --- a/contrib/xdp-filter.ebpf.src +++ b/contrib/xdp-filter.ebpf.src @@ -143,8 +143,8 @@ static inline struct map_value* check_qname(struct cursor* c) return value; } - // check with Qtype 255 (*) - qkey.qtype = 255; + // check with Qtype 65535 (*) + qkey.qtype = 65535; return qnamefilter.lookup(&qkey); } diff --git a/contrib/xdp.py b/contrib/xdp.py index e5fb69ce40..5365e8e971 100644 --- a/contrib/xdp.py +++ b/contrib/xdp.py @@ -7,8 +7,9 @@ import netaddr from bcc import BPF # Constants -QTYPES = {'LOC': 29, - '*': 255, +QTYPES = {'*': 65535, + 'LOC': 29, + 'ANY': 255, 'IXFR': 251, 'UINFO': 100, 'NSEC3': 50, diff --git a/pdns/dnsdistdist/bpf-filter.ebpf.src b/pdns/dnsdistdist/bpf-filter.ebpf.src index 9f58669068..2f77ba0300 100644 --- a/pdns/dnsdistdist/bpf-filter.ebpf.src +++ b/pdns/dnsdistdist/bpf-filter.ebpf.src @@ -389,7 +389,7 @@ int bpf_qname_filter(struct __sk_buff *skb) struct QNameValue* qvalue = qnamefilter.lookup(&qkey); if (qvalue && - (qvalue->qtype == 255 || qtype == qvalue->qtype)) { + (qvalue->qtype == 65535 || qtype == qvalue->qtype)) { __sync_fetch_and_add(&qvalue->counter, 1); return 0; } @@ -479,7 +479,7 @@ int bpf_dns_filter(struct __sk_buff *skb) { struct QNameValue* qvalue = qnamefilter.lookup(&qkey); if (qvalue && - (qvalue->qtype == 255 || qtype == qvalue->qtype)) { + (qvalue->qtype == 65535 || qtype == qvalue->qtype)) { __sync_fetch_and_add(&qvalue->counter, 1); return 0; } diff --git a/pdns/dnsdistdist/bpf-filter.hh b/pdns/dnsdistdist/bpf-filter.hh index 46b7eed9dc..eaca10e194 100644 --- a/pdns/dnsdistdist/bpf-filter.hh +++ b/pdns/dnsdistdist/bpf-filter.hh @@ -90,10 +90,10 @@ public: void removeSocket(int sock); void block(const ComboAddress& addr, MatchAction action); void addRangeRule(const Netmask& address, bool force, BPFFilter::MatchAction action); - void block(const DNSName& qname, MatchAction action, uint16_t qtype = 255); + void block(const DNSName& qname, MatchAction action, uint16_t qtype = 65535); void unblock(const ComboAddress& addr); void rmRangeRule(const Netmask& address); - void unblock(const DNSName& qname, uint16_t qtype = 255); + void unblock(const DNSName& qname, uint16_t qtype = 65535); std::vector> getAddrStats(); std::vector> getRangeRule(); diff --git a/pdns/dnsdistdist/bpf-filter.main.ebpf b/pdns/dnsdistdist/bpf-filter.main.ebpf index 4b42a2ec6b..ef0e858434 100644 --- a/pdns/dnsdistdist/bpf-filter.main.ebpf +++ b/pdns/dnsdistdist/bpf-filter.main.ebpf @@ -104,7 +104,7 @@ BPF_RAW_INSN(BPF_JMP|BPF_CALL,0,0,0,BPF_FUNC_map_lookup_elem), BPF_MOV64_IMM(BPF_REG_7,2147483647), BPF_JMP_IMM(BPF_JEQ,BPF_REG_0,0,7), BPF_LDX_MEM(BPF_H,BPF_REG_1,BPF_REG_0,8), -BPF_JMP_IMM(BPF_JEQ,BPF_REG_1,255,2), +BPF_JMP_IMM(BPF_JEQ,BPF_REG_1,65535,2), BPF_ALU64_IMM(BPF_AND,BPF_REG_6,65535), BPF_JMP_REG(BPF_JNE,BPF_REG_1,BPF_REG_6,3), BPF_MOV64_IMM(BPF_REG_1,1), diff --git a/pdns/dnsdistdist/bpf-filter.qname.ebpf b/pdns/dnsdistdist/bpf-filter.qname.ebpf index c7d6094c08..120e0620e2 100644 --- a/pdns/dnsdistdist/bpf-filter.qname.ebpf +++ b/pdns/dnsdistdist/bpf-filter.qname.ebpf @@ -4085,7 +4085,7 @@ BPF_RAW_INSN(BPF_JMP|BPF_CALL,0,0,0,BPF_FUNC_map_lookup_elem), BPF_MOV64_IMM(BPF_REG_1,2147483647), BPF_JMP_IMM(BPF_JEQ,BPF_REG_0,0,7), BPF_LDX_MEM(BPF_H,BPF_REG_2,BPF_REG_0,8), -BPF_JMP_IMM(BPF_JEQ,BPF_REG_2,255,2), +BPF_JMP_IMM(BPF_JEQ,BPF_REG_2,65535,2), BPF_ALU64_IMM(BPF_AND,BPF_REG_6,65535), BPF_JMP_REG(BPF_JNE,BPF_REG_6,BPF_REG_2,3), BPF_MOV64_IMM(BPF_REG_1,1), diff --git a/pdns/dnsdistdist/dnsdist-lua-bindings.cc b/pdns/dnsdistdist/dnsdist-lua-bindings.cc index 8f966b1a12..a7b5458f2f 100644 --- a/pdns/dnsdistdist/dnsdist-lua-bindings.cc +++ b/pdns/dnsdistdist/dnsdist-lua-bindings.cc @@ -579,7 +579,7 @@ void setupLuaBindings(LuaContext& luaCtx, bool client, bool configCheck) luaCtx.registerFunction::*)(const DNSName& qname, boost::optional qtype, boost::optional action)>("blockQName", [](const std::shared_ptr& bpf, const DNSName& qname, boost::optional qtype, boost::optional action) { if (bpf) { if (!action) { - return bpf->block(qname, BPFFilter::MatchAction::Drop, qtype ? *qtype : 255); + return bpf->block(qname, BPFFilter::MatchAction::Drop, qtype ? *qtype : 65535); } BPFFilter::MatchAction match{}; @@ -596,7 +596,7 @@ void setupLuaBindings(LuaContext& luaCtx, bool client, bool configCheck) default: throw std::runtime_error("Unsupported action for BPFFilter::blockQName"); } - return bpf->block(qname, match, qtype ? *qtype : 255); + return bpf->block(qname, match, qtype ? *qtype : 65535); } }); @@ -630,7 +630,7 @@ void setupLuaBindings(LuaContext& luaCtx, bool client, bool configCheck) }); luaCtx.registerFunction::*)(const DNSName& qname, boost::optional qtype)>("unblockQName", [](const std::shared_ptr& bpf, const DNSName& qname, boost::optional qtype) { if (bpf) { - return bpf->unblock(qname, qtype ? *qtype : 255); + return bpf->unblock(qname, qtype ? *qtype : 65535); } }); diff --git a/pdns/dnsdistdist/docs/advanced/ebpf.rst b/pdns/dnsdistdist/docs/advanced/ebpf.rst index 5b76b91b98..f82eea95f3 100644 --- a/pdns/dnsdistdist/docs/advanced/ebpf.rst +++ b/pdns/dnsdistdist/docs/advanced/ebpf.rst @@ -31,16 +31,20 @@ The BPF filter can be used to block incoming queries manually:: > bpf = newBPFFilter({ipv4MaxItems=1024, ipv6MaxItems=1024, qnamesMaxItems=1024}) > bpf:attachToAllBinds() > bpf:block(newCA("2001:DB8::42")) - > bpf:blockQName(newDNSName("evildomain.com"), 255) + > bpf:blockQName(newDNSName("evildomain.com"), 65535) > bpf:getStats() [2001:DB8::42]: 0 - evildomain.com. 255: 0 + evildomain.com. 65535: 0 > bpf:unblock(newCA("2001:DB8::42")) - > bpf:unblockQName(newDNSName("evildomain.com"), 255) + > bpf:unblockQName(newDNSName("evildomain.com"), 65535) > bpf:getStats() +.. note:: + Before 2.0.0 the value used to block queries for all types was 255. This was changed because it prevented blocking only queries for the ``ANY`` (255) qtype. + The :meth:`BPFFilter:blockQName` method can be used to block queries based on the exact qname supplied, in a case-insensitive way, and an optional qtype. -Using the 255 (ANY) qtype will block all queries for the qname, regardless of the qtype. +Using the ``65535`` value for the qtype will block all queries for the qname, regardless of the qtype. + Contrary to source address filtering, qname filtering only works over UDP. TCP qname filtering can be done the usual way:: addAction(AndRule({TCPRule(true), QNameSuffixRule("evildomain.com")}), DropAction()) diff --git a/pdns/dnsdistdist/docs/reference/ebpf.rst b/pdns/dnsdistdist/docs/reference/ebpf.rst index 6e5902f46f..0f95511d5c 100644 --- a/pdns/dnsdistdist/docs/reference/ebpf.rst +++ b/pdns/dnsdistdist/docs/reference/ebpf.rst @@ -87,7 +87,7 @@ These are all the functions, objects and methods related to the :doc:`../advance .. versionadded:: 1.8.0 - Block all IP addresses in this range. + Block all IP addresses in this range. DNSDist eBPF code first checks if an exact IP match is found, then if a range matches, and finally if a DNSName does. @@ -95,9 +95,12 @@ These are all the functions, objects and methods related to the :doc:`../advance :param int action: set ``action`` to ``0`` to allow a range, set ``action`` to ``1`` to block a range, set ``action`` to ``2`` to truncate a range. :param bool force: When ``force`` is set to true, DNSDist always accepts adding a new item to BPF maps, even if the item to be added may already be included in the larger network range. - .. method:: BPFFilter:blockQName(name [, qtype=255]) + .. method:: BPFFilter:blockQName(name [, qtype=65535]) - Block queries for this exact qname. An optional qtype can be used, defaults to 255. + .. versionchanged:: 2.0.0 + Before 2.0.0 the value used to block queries for all types was 255. It also used to be the default value. This was changed because it prevented blocking only queries for the ``ANY`` (255) qtype. + + Block queries for this exact qname. An optional qtype can be used, defaults to 65535 which blocks queries for all types. :param DNSName name: The name to block :param int qtype: QType to block @@ -124,7 +127,10 @@ These are all the functions, objects and methods related to the :doc:`../advance List all range rule. - .. method:: BPFFilter:unblockQName(name [, qtype=255]) + .. method:: BPFFilter:unblockQName(name [, qtype=65535]) + + .. versionchanged:: 2.0.0 + Before 2.0.0 the value used to block queries for all types was 255. It also used to be the default value. This was changed because it prevented blocking only queries for the ``ANY`` (255) qtype. Remove this qname from the block list. diff --git a/pdns/dnsdistdist/docs/upgrade_guide.rst b/pdns/dnsdistdist/docs/upgrade_guide.rst index 427be84a3e..cecd03104a 100644 --- a/pdns/dnsdistdist/docs/upgrade_guide.rst +++ b/pdns/dnsdistdist/docs/upgrade_guide.rst @@ -9,6 +9,8 @@ Upgrade Guide :func:`showTLSContexts` has been renamed to :func:`showTLSFrontends`. :func:`getTLSContext` and the associated :class:`TLSContext` have been removed, please use :func:`getTLSFrontend` and the associated :class:`TLSFrontend` instead. +Our eBPF filtering code no longer treats the ``255``/``ANY`` qtype as a special value intended to block queries for all types, and will only block ``ANY`` queries instead. The reserved ``65535`` value now can be used to block queries for all qtypes. + 1.8.x to 1.9.0 -------------- diff --git a/regression-tests.dnsdist/test_EBPF.py b/regression-tests.dnsdist/test_EBPF.py index e8ca411788..26d8912f68 100644 --- a/regression-tests.dnsdist/test_EBPF.py +++ b/regression-tests.dnsdist/test_EBPF.py @@ -31,7 +31,8 @@ class TestSimpleEBPF(DNSDistTest): bpf = newBPFFilter({ipv4MaxItems=10, ipv6MaxItems=10, qnamesMaxItems=10}) setDefaultBPFFilter(bpf) - bpf:blockQName(newDNSName("blocked.ebpf.tests.powerdns.com."), 255) + bpf:blockQName(newDNSName("blocked.ebpf.tests.powerdns.com."), 65535) + bpf:blockQName(newDNSName("blocked-any-only.ebpf.tests.powerdns.com."), 255) addTLSLocal("127.0.0.1:%d", "%s", "%s", { provider="openssl" }) addDOHLocal("127.0.0.1:%d", "%s", "%s", {"/"}, {library="nghttp2"}) @@ -95,6 +96,35 @@ class TestSimpleEBPF(DNSDistTest): receivedResponse.id = response.id self.assertEqual(response, receivedResponse) + def testQNameBlockedOnylForAny(self): + # unblock 127.0.0.1, just in case + self.sendConsoleCommand('bpf:unblock(newCA("127.0.0.1"))') + + name = 'blocked-any-only.ebpf.tests.powerdns.com.' + query = dns.message.make_query(name, 'ANY', 'IN', use_edns=False) + + # ANY should be blocked over Do53 UDP + for method in ["sendUDPQuery"]: + sender = getattr(self, method) + (_, receivedResponse) = sender(query, response=None, useQueue=False, timeout=0.5) + self.assertEqual(receivedResponse, None) + + query = dns.message.make_query(name, 'A', 'IN', use_edns=False) + response = dns.message.make_response(query) + rrset = dns.rrset.from_text(name, + 3600, + dns.rdataclass.IN, + dns.rdatatype.A, + '127.0.0.1') + response.answer.append(rrset) + # but A should NOT be blocked + for method in ["sendUDPQuery"]: + sender = getattr(self, method) + (receivedQuery, receivedResponse) = sender(query, response, timeout=1) + receivedQuery.id = query.id + self.assertEqual(query, receivedQuery) + self.assertEqual(response, receivedResponse) + def testClientIPBlocked(self): # block 127.0.0.1 self.sendConsoleCommand('bpf:block(newCA("127.0.0.1"))')