]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Add test and fix TC=1 adding of SOA record
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Tue, 30 May 2023 12:24:33 +0000 (14:24 +0200)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Wed, 31 May 2023 12:46:29 +0000 (14:46 +0200)
pdns/recursordist/docs/lua-config/rpz.rst
pdns/recursordist/pdns_recursor.cc
pdns/recursordist/syncres.cc
regression-tests.recursor-dnssec/test_RPZ.py

index ee0e388179ce57d7f905973e7c5a6388ec883ee7..47e1ac957f29fac60c07ec803420766932d4ace1 100644 (file)
@@ -151,6 +151,13 @@ extendedErrorExtra
 
 An extended error extra text (:rfc:`8914`) to set on RPZ hits. See :ref:`setting-extended-resolution-errors`.
 
+includeSOA
+^^^^^^^^^^
+.. versionadded:: 4.9.0
+
+Include the RPZ's SOA record to the reply's additional section if modified by a policy hit.
+Defaults to ``no``.
+
 maxTTL
 ^^^^^^
 The maximum TTL value of the synthesized records, overriding a higher value from ``defttl`` or the zone. Default is unlimited.
index 9df794155c60493c18514b5610b12267f410f2de..0ad5b91ed8a1ce24d05638dafb5f00b4c7186023 100644 (file)
@@ -1249,6 +1249,7 @@ void startDoResolve(void* p) // NOLINT(readability-function-cognitive-complexity
       }
       catch (const SendTruncatedAnswerException& e) {
         ret.clear();
+        sr.d_appliedPolicy.addSOAtoRPZResult(ret);
         res = RCode::NoError;
         pw.getHeader()->tc = 1;
       }
index 1472266b8900aabd16959c10b8cf7c5eafc9dc0c..5fc54d5250c32e82a10faf901822eddc8e58f3a6 100644 (file)
@@ -3245,8 +3245,9 @@ void SyncRes::handlePolicyHit(const std::string& prefix, const DNSName& qname, c
   case DNSFilterEngine::PolicyKind::Truncate:
     if (!d_queryReceivedOverTCP) {
       ret.clear();
-      d_appliedPolicy.addSOAtoRPZResult(ret);
       rcode = RCode::NoError;
+      // Exception handling code in pdns_recursor clears ret as well, so no use to
+      // fill it here.
       throw SendTruncatedAnswerException();
     }
     return;
index 85268e447232533cd2b4595f29bbf35a2d9be8a9..4215eacd33fbe4fd2f8a3804146211b990889a35 100644 (file)
@@ -248,7 +248,20 @@ api-key=%s
 log-rpz-changes=yes
 """ % (_confdir, _wsPort, _wsPassword, _apiKey)
 
-    def checkBlocked(self, name, shouldBeBlocked=True, adQuery=False, singleCheck=False):
+    def assertAdditionalHasSOA(self, msg):
+        if not isinstance(msg, dns.message.Message):
+            raise TypeError("msg is not a dns.message.Message but a %s" % type(msg))
+
+        found = False
+        for rrset in msg.additional:
+            if rrset.rdtype == dns.rdatatype.SOA:
+                found = True
+                break
+
+        if not found:
+            raise AssertionError("No SOA record found in the authority section:\n%s" % msg.to_text())
+
+    def checkBlocked(self, name, shouldBeBlocked=True, adQuery=False, singleCheck=False, soa=False):
         query = dns.message.make_query(name, 'A', want_dnssec=True)
         query.flags |= dns.flags.CD
         if adQuery:
@@ -264,13 +277,15 @@ log-rpz-changes=yes
                 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
 
             self.assertRRsetInAnswer(res, expected)
+            if soa:
+                self.assertAdditionalHasSOA(res)
             if singleCheck:
                 break
 
     def checkNotBlocked(self, name, adQuery=False, singleCheck=False):
         self.checkBlocked(name, False, adQuery, singleCheck)
 
-    def checkCustom(self, qname, qtype, expected):
+    def checkCustom(self, qname, qtype, expected, soa=False):
         query = dns.message.make_query(qname, qtype, want_dnssec=True)
         query.flags |= dns.flags.CD
         for method in ("sendUDPQuery", "sendTCPQuery"):
@@ -278,8 +293,10 @@ log-rpz-changes=yes
             res = sender(query)
             self.assertRcodeEqual(res, dns.rcode.NOERROR)
             self.assertRRsetInAnswer(res, expected)
+            if soa:
+                self.assertAdditionalHasSOA(res)
 
-    def checkNoData(self, qname, qtype):
+    def checkNoData(self, qname, qtype, soa=False):
         query = dns.message.make_query(qname, qtype, want_dnssec=True)
         query.flags |= dns.flags.CD
         for method in ("sendUDPQuery", "sendTCPQuery"):
@@ -287,6 +304,8 @@ log-rpz-changes=yes
             res = sender(query)
             self.assertRcodeEqual(res, dns.rcode.NOERROR)
             self.assertEqual(len(res.answer), 0)
+            if soa:
+                self.assertAdditionalHasSOA(res)
 
     def checkNXD(self, qname, qtype='A'):
         query = dns.message.make_query(qname, qtype, want_dnssec=True)
@@ -298,7 +317,7 @@ log-rpz-changes=yes
             self.assertEqual(len(res.answer), 0)
             self.assertEqual(len(res.authority), 1)
 
-    def checkTruncated(self, qname, qtype='A'):
+    def checkTruncated(self, qname, qtype='A', soa=False):
         query = dns.message.make_query(qname, qtype, want_dnssec=True)
         query.flags |= dns.flags.CD
         res = self.sendUDPQuery(query)
@@ -306,7 +325,8 @@ log-rpz-changes=yes
         self.assertMessageHasFlags(res, ['QR', 'RA', 'RD', 'CD', 'TC'])
         self.assertEqual(len(res.answer), 0)
         self.assertEqual(len(res.authority), 0)
-        self.assertEqual(len(res.additional), 0)
+        if soa:
+            self.assertAdditionalHasSOA(res)
 
         res = self.sendTCPQuery(query)
         self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
@@ -352,7 +372,7 @@ class RPZXFRRecursorTest(RPZRecursorTest):
     global rpzServerPort
     _lua_config_file = """
     -- The first server is a bogus one, to test that we correctly fail over to the second one
-    rpzMaster({'127.0.0.1:9999', '127.0.0.1:%d'}, 'zone.rpz.', { refresh=1 })
+    rpzMaster({'127.0.0.1:9999', '127.0.0.1:%d'}, 'zone.rpz.', { refresh=1, includeSOA=true})
     """ % (rpzServerPort)
     _confdir = 'RPZXFR'
     _wsPort = 8042
@@ -408,22 +428,22 @@ e 3600 IN A 192.0.2.42
         # first zone, only a should be blocked
         self.waitUntilCorrectSerialIsLoaded(1)
         self.checkRPZStats(1, 1, 1, self._xfrDone)
-        self.checkBlocked('a.example.')
+        self.checkBlocked('a.example.', soa=True)
         self.checkNotBlocked('b.example.')
         self.checkNotBlocked('c.example.')
 
         # second zone, a and b should be blocked
         self.waitUntilCorrectSerialIsLoaded(2)
         self.checkRPZStats(2, 2, 1, self._xfrDone)
-        self.checkBlocked('a.example.')
-        self.checkBlocked('b.example.')
+        self.checkBlocked('a.example.', soa=True)
+        self.checkBlocked('b.example.', soa=True)
         self.checkNotBlocked('c.example.')
 
         # third zone, only b should be blocked
         self.waitUntilCorrectSerialIsLoaded(3)
         self.checkRPZStats(3, 1, 1, self._xfrDone)
         self.checkNotBlocked('a.example.')
-        self.checkBlocked('b.example.')
+        self.checkBlocked('b.example.', soa=True)
         self.checkNotBlocked('c.example.')
 
         # fourth zone, only c should be blocked
@@ -431,7 +451,7 @@ e 3600 IN A 192.0.2.42
         self.checkRPZStats(4, 1, 1, self._xfrDone)
         self.checkNotBlocked('a.example.')
         self.checkNotBlocked('b.example.')
-        self.checkBlocked('c.example.')
+        self.checkBlocked('c.example.', soa=True)
 
         # fifth zone, we should get a full AXFR this time, and only d should be blocked
         self.waitUntilCorrectSerialIsLoaded(5)
@@ -439,7 +459,7 @@ e 3600 IN A 192.0.2.42
         self.checkNotBlocked('a.example.')
         self.checkNotBlocked('b.example.')
         self.checkNotBlocked('c.example.')
-        self.checkBlocked('d.example.')
+        self.checkBlocked('d.example.', soa=True)
 
         # sixth zone, only e should be blocked, f is a local data record
         self.waitUntilCorrectSerialIsLoaded(6)
@@ -448,10 +468,10 @@ e 3600 IN A 192.0.2.42
         self.checkNotBlocked('b.example.')
         self.checkNotBlocked('c.example.')
         self.checkNotBlocked('d.example.')
-        self.checkCustom('e.example.', 'A', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.1', '192.0.2.2'))
+        self.checkCustom('e.example.', 'A', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.1', '192.0.2.2'), soa=True)
         self.checkCustom('e.example.', 'MX', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'MX', '10 mx.example.'))
-        self.checkNoData('e.example.', 'AAAA')
-        self.checkCustom('f.example.', 'A', dns.rrset.from_text('f.example.', 0, dns.rdataclass.IN, 'CNAME', 'e.example.'))
+        self.checkNoData('e.example.', 'AAAA', soa=True)
+        self.checkCustom('f.example.', 'A', dns.rrset.from_text('f.example.', 0, dns.rdataclass.IN, 'CNAME', 'e.example.'), soa=True)
 
         # seventh zone, e should only have one A
         self.waitUntilCorrectSerialIsLoaded(7)
@@ -460,14 +480,14 @@ e 3600 IN A 192.0.2.42
         self.checkNotBlocked('b.example.')
         self.checkNotBlocked('c.example.')
         self.checkNotBlocked('d.example.')
-        self.checkCustom('e.example.', 'A', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.2'))
-        self.checkCustom('e.example.', 'MX', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'MX', '10 mx.example.'))
-        self.checkNoData('e.example.', 'AAAA')
-        self.checkCustom('f.example.', 'A', dns.rrset.from_text('f.example.', 0, dns.rdataclass.IN, 'CNAME', 'e.example.'))
+        self.checkCustom('e.example.', 'A', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.2'), soa=True)
+        self.checkCustom('e.example.', 'MX', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'MX', '10 mx.example.'), soa=True)
+        self.checkNoData('e.example.', 'AAAA', soa=True)
+        self.checkCustom('f.example.', 'A', dns.rrset.from_text('f.example.', 0, dns.rdataclass.IN, 'CNAME', 'e.example.'), soa=True)
         # check that the policy is disabled for AD=1 queries
         self.checkNotBlocked('e.example.', True)
         # check non-custom policies
-        self.checkTruncated('tc.example.')
+        self.checkTruncated('tc.example.', soa=True)
         self.checkDropped('drop.example.')
 
         # eighth zone, all entries should be gone
@@ -493,7 +513,7 @@ e 3600 IN A 192.0.2.42
         self.checkNotBlocked('c.example.')
         self.checkNotBlocked('d.example.')
         self.checkNotBlocked('e.example.')
-        self.checkBlocked('f.example.')
+        self.checkBlocked('f.example.', soa=True)
         self.checkNXD('tc.example.')
         self.checkNXD('drop.example.')
 
@@ -508,7 +528,7 @@ e 3600 IN A 192.0.2.42
         self.checkNotBlocked('d.example.')
         self.checkNotBlocked('e.example.')
         self.checkNXD('f.example.')
-        self.checkBlocked('g.example.')
+        self.checkBlocked('g.example.', soa=True)
         self.checkNXD('tc.example.')
         self.checkNXD('drop.example.')
 
@@ -519,7 +539,7 @@ class RPZFileRecursorTest(RPZRecursorTest):
 
     _confdir = 'RPZFile'
     _lua_config_file = """
-    rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz." })
+    rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz.", includeSOA=true })
     """ % (_confdir)
     _config_template = """
 auth-zones=example=configs/%s/example.zone
@@ -555,7 +575,7 @@ tc.example.zone.rpz. 60 IN CNAME rpz-tcp-only.
     def testRPZ(self):
         self.checkCustom('a.example.', 'A', dns.rrset.from_text('a.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.42', '192.0.2.43'))
         self.checkCustom('a.example.', 'TXT', dns.rrset.from_text('a.example.', 0, dns.rdataclass.IN, 'TXT', '"some text"'))
-        self.checkBlocked('z.example.')
+        self.checkBlocked('z.example.', soa=True)
         self.checkNotBlocked('b.example.')
         self.checkNotBlocked('c.example.')
         self.checkNotBlocked('d.example.')
@@ -563,7 +583,7 @@ tc.example.zone.rpz. 60 IN CNAME rpz-tcp-only.
         # check that the policy is disabled for AD=1 queries
         self.checkNotBlocked('z.example.', True)
         # check non-custom policies
-        self.checkTruncated('tc.example.')
+        self.checkTruncated('tc.example.', soa=True)
         self.checkDropped('drop.example.')
 
 class RPZFileDefaultPolRecursorTest(RPZRecursorTest):