From: Otto Moerbeek Date: Tue, 30 May 2023 12:24:33 +0000 (+0200) Subject: Add test and fix TC=1 adding of SOA record X-Git-Tag: rec-4.10.0-alpha0~7^2~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f6a524be8b4fb6b211ff6879e764347e179a3f54;p=thirdparty%2Fpdns.git Add test and fix TC=1 adding of SOA record --- diff --git a/pdns/recursordist/docs/lua-config/rpz.rst b/pdns/recursordist/docs/lua-config/rpz.rst index ee0e388179..47e1ac957f 100644 --- a/pdns/recursordist/docs/lua-config/rpz.rst +++ b/pdns/recursordist/docs/lua-config/rpz.rst @@ -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. diff --git a/pdns/recursordist/pdns_recursor.cc b/pdns/recursordist/pdns_recursor.cc index 9df794155c..0ad5b91ed8 100644 --- a/pdns/recursordist/pdns_recursor.cc +++ b/pdns/recursordist/pdns_recursor.cc @@ -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; } diff --git a/pdns/recursordist/syncres.cc b/pdns/recursordist/syncres.cc index 1472266b89..5fc54d5250 100644 --- a/pdns/recursordist/syncres.cc +++ b/pdns/recursordist/syncres.cc @@ -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; diff --git a/regression-tests.recursor-dnssec/test_RPZ.py b/regression-tests.recursor-dnssec/test_RPZ.py index 85268e4472..4215eacd33 100644 --- a/regression-tests.recursor-dnssec/test_RPZ.py +++ b/regression-tests.recursor-dnssec/test_RPZ.py @@ -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):