]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Use 65535 instead of 255 to block all types via eBPF 15199/head
authorRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 21 Feb 2025 13:38:55 +0000 (14:38 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 21 Feb 2025 13:43:54 +0000 (14:43 +0100)
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.

contrib/xdp-filter.ebpf.src
contrib/xdp.py
pdns/dnsdistdist/bpf-filter.ebpf.src
pdns/dnsdistdist/bpf-filter.hh
pdns/dnsdistdist/bpf-filter.main.ebpf
pdns/dnsdistdist/bpf-filter.qname.ebpf
pdns/dnsdistdist/dnsdist-lua-bindings.cc
pdns/dnsdistdist/docs/advanced/ebpf.rst
pdns/dnsdistdist/docs/reference/ebpf.rst
pdns/dnsdistdist/docs/upgrade_guide.rst
regression-tests.dnsdist/test_EBPF.py

index f25ba2c08e388911d9f1a2863b4086b928c9a368..31275d2ff738f7967f69f120da5ca6d028b131ab 100644 (file)
@@ -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);
 }
index e5fb69ce40a575065793e596c6230b8e6740c711..5365e8e971f14a64545ef2012ec74b4790147df0 100644 (file)
@@ -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,
index 9f58669068761bf0b271eddd4e01653cdf1806d0..2f77ba03004c59fbaff899880c1231d8bc67bde8 100644 (file)
@@ -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;
     }
index 46b7eed9dc1bb3ed7fe11ceea2ad7fe4764adf3a..eaca10e1947967af00cf531b23e75786b3291e54 100644 (file)
@@ -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<std::pair<ComboAddress, uint64_t>> getAddrStats();
   std::vector<std::pair<Netmask, CounterAndActionValue>> getRangeRule();
index 4b42a2ec6b71ea0237e7808d2782716bfee9b35f..ef0e85843436d93889a69df7e342b7d70932aa7f 100644 (file)
@@ -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),
index c7d6094c0872ed85619131878bdc6b595d7ba037..120e0620e2b7a14d23e635de60ebd4f382ac85b5 100644 (file)
@@ -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),
index 8f966b1a124f512ea759a687374e5eb8214d7e37..a7b5458f2f4651dc1e0a87e82881ecee07b94501 100644 (file)
@@ -579,7 +579,7 @@ void setupLuaBindings(LuaContext& luaCtx, bool client, bool configCheck)
   luaCtx.registerFunction<void (std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype, boost::optional<uint32_t> action)>("blockQName", [](const std::shared_ptr<BPFFilter>& bpf, const DNSName& qname, boost::optional<uint16_t> qtype, boost::optional<uint32_t> 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<void (std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype)>("unblockQName", [](const std::shared_ptr<BPFFilter>& bpf, const DNSName& qname, boost::optional<uint16_t> qtype) {
     if (bpf) {
-      return bpf->unblock(qname, qtype ? *qtype : 255);
+      return bpf->unblock(qname, qtype ? *qtype : 65535);
     }
   });
 
index 5b76b91b98e8dc5b5f61fed369ae5a8f91a7f375..f82eea95f3d91aaec6dd06b290a1f48ef71cc734 100644 (file)
@@ -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())
index 6e5902f46f13d9a09ebca8821c9c64dd512b82ff..0f95511d5cd48fb6b259779544d6dfd6de641571 100644 (file)
@@ -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.
 
index 427be84a3e243fa005763a79948586c9260cdaa2..cecd03104a72d546bef29dea4ae6d12c65b6ba8e 100644 (file)
@@ -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
 --------------
 
index e8ca41178858d23f80843436c3b05b79099fd1a4..26d8912f68f9ea95ee724ca1f8b0644f1cd64f0f 100644 (file)
@@ -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"))')