1 # Copyright (C) Internet Systems Consortium, Inc. ("ISC") 2 # 3 # SPDX-License-Identifier: MPL-2.0 4 # 5 # This Source Code Form is subject to the terms of the Mozilla Public 6 # License, v. 2.0. If a copy of the MPL was not distributed with this 7 # file, you can obtain one at https://mozilla.org/MPL/2.0/. 8 # 9 # See the COPYRIGHT file distributed with this work for additional 10 # information regarding copyright ownership. 11 12 import dns.message 13 import dns.name 14 import dns.rdatatype 15 16 import isctest 17 18 19 def _query(ns, qname, qtype): 20 msg = isctest.query.create(qname, qtype) 21 res = isctest.query.udp(msg, ns.ip) 22 isctest.check.noerror(res) 23 return res 24 25 26 def _additional(res, target): 27 """ 28 Return the additional RRsets of 'res' keyed by type, checking that 29 all of them are owned by 'target'. 30 """ 31 owner = dns.name.from_text(target) 32 assert all(rrset.name == owner for rrset in res.additional), res.additional 33 return {rrset.rdtype: rrset for rrset in res.additional} 34 35 36 def test_https_alias_target_too_many_records(ns3): 37 """ 38 Resolve HTTPS AliasMode records whose targets are already cached, then 39 shut named down and check that nothing was leaked. 40 41 The target of alias14 is a 14-record ServiceMode RRset, i.e. more than 42 DNS_RDATASET_MAXADDITIONAL. Following the alias clones the cached 43 target RRset into the caller's rdataset, and the subsequent additional 44 processing of that RRset fails with DNS_R_TOOMANYRECORDS. That error 45 used to be returned before the clone was disassociated, leaking a 46 reference to the cache node and its slab for every such query. 47 48 The target of alias13 is at the limit and is processed normally. 49 50 The limit bounds the processing of a target RRset for its own 51 additional data, not the inclusion of the target RRset itself, which 52 is added to the response before it is processed. The ServiceMode 53 records point at "." (the owner name), so processing a target RRset 54 adds the target's cached A record: it is present for target13 and 55 absent for target14. 56 """ 57 # Prime the cache with both target RRsets and their A records. 58 res = _query(ns3, "target14.https.example.", "HTTPS") 59 isctest.check.rr_count_eq(res.answer, 14) 60 res = _query(ns3, "target13.https.example.", "HTTPS") 61 isctest.check.rr_count_eq(res.answer, 13) 62 for target in ("target14", "target13"): 63 res = _query(ns3, f"{target}.https.example.", "A") 64 isctest.check.rr_count_eq(res.answer, 1) 65 66 # The first pass resolves the aliases recursively, the second one is 67 # answered from the cache. 68 for _ in range(2): 69 # An error while collecting the additional data must not turn into 70 # a failed response (RFC 9460 section 4.2). 71 res = _query(ns3, "alias14.https.example.", "HTTPS") 72 expected = dns.message.from_text(""";ANSWER 73 alias14.https.example. 86400 IN HTTPS 0 target14.https.example. 74 """) 75 isctest.check.rrsets_equal(res.answer, expected.answer) 76 # The target RRset is returned in full, but as it is over the 77 # limit it is not processed, so its A record is not added. 78 target14 = _additional(res, "target14.https.example.") 79 assert set(target14) == {dns.rdatatype.HTTPS}, res.additional 80 isctest.check.rr_count_eq([target14[dns.rdatatype.HTTPS]], 14) 81 82 # The alias at the limit is followed, its target is included, and 83 # the target is processed in turn, adding its A record. 84 res = _query(ns3, "alias13.https.example.", "HTTPS") 85 expected = dns.message.from_text(""";ANSWER 86 alias13.https.example. 86400 IN HTTPS 0 target13.https.example. 87 """) 88 isctest.check.rrsets_equal(res.answer, expected.answer) 89 target13 = _additional(res, "target13.https.example.") 90 assert set(target13) == {dns.rdatatype.HTTPS, dns.rdatatype.A}, res.additional 91 isctest.check.rr_count_eq([target13[dns.rdatatype.HTTPS]], 13) 92 expected = dns.message.from_text(""";ADDITIONAL 93 target13.https.example. 86400 IN A 10.53.0.13 94 """) 95 isctest.check.rrsets_equal([target13[dns.rdatatype.A]], expected.additional) 96 97 # Stop the server and check for leaked references. A leaked cache 98 # rdataset pins the cache database and its memory context, which shows 99 # up in named's memory tracking output at exit. 100 ns3.stop() 101 assert "outstanding memory" not in ns3.log 102