def noqname_test(server, name: dns.name.Name, named_port: int) -> None:
- # Name must not exist.
- all_existing_names = (
- ZONE.reachable.union(ZONE.ents).union(ZONE.delegations).union(ZONE.dnames)
- )
- assume(name not in (all_existing_names))
+ # randomly generated name must not exist
+ assume(name not in (ZONE.all_existing_names))
- # Name must not be below a delegation or DNAME.
+ # name must not be under a delegation or DNAME:
+ # it would not work with resolver ns4
assume(
not isctest.name.is_related_to_any(
name,
isctest.check.is_response_to(response, query)
assert response.rcode() in (dns.rcode.NOERROR, dns.rcode.NXDOMAIN)
- # Retrieve closest encloser (ce) and next closest encloser (nce).
- ce = None
- nce = None
- if response.rcode() is dns.rcode.NOERROR:
- # this should only be a wild card response
+ ce, nce = ZONE.closest_encloser(name)
+ # Response has NSEC3 that covers the next closer name
+ check_nsec3_covers(nce, response)
+
+ wname = ZONE.source_of_synthesis(name)
+ if wname in ZONE.reachable_wildcards:
+ wname_parent = dns.name.Name(wname[1:])
+ assert name.is_subdomain(wname_parent)
+ # expecting wildcard response with a signed A RRset
+ assert response.rcode() is dns.rcode.NOERROR
answer_sig = response.get_rrset(
section="ANSWER",
name=name,
)
assert answer_sig is not None
assert len(answer_sig) == 1
- # root label is not being counted in labels field, RFC 4034 section 3.1.3
- ce_labels = answer_sig[0].labels + 1
- # wildcard labels < QNAME labels
- assert ce_labels < len(name.labels)
- # ce is wildcard name w/o wildcard label
- _, ce = name.split(ce_labels)
- _, nce = name.split(ce_labels + 1)
+ # RRSIG labels field, RFC 4034 section 3.1.3 does not count:
+ # - root label
+ # - leftmost * label
+ wildcard_parent_labels = answer_sig[0].labels + 1 # add root but not leftmost *
+ assert wildcard_parent_labels < len(name)
+ # ce should be wildcard name w/o wildcard label, nce one label longer
+ assert ce == name.split(wildcard_parent_labels)[1]
+ assert nce == name.split(wildcard_parent_labels + 1)[1]
else:
- ce_labels = 0
- for zname in all_existing_names:
- relation, _, nlabels = name.fullcompare(zname)
- if relation == dns.name.NameRelation.SUBDOMAIN:
- if nlabels > ce_labels:
- ce_labels = nlabels
- ce = zname
- _, nce = name.split(ce_labels + 1)
- assert ce is not None
- assert nce is not None
-
- # Response has closest encloser NSEC3.
+ # no wildcard synthesis -> NXDOMAIN
+ assert response.rcode() is dns.rcode.NXDOMAIN
+ # Response must have closest encloser NSEC3
ce_hash = dns.dnssec.nsec3_hash(
ce, salt=None, iterations=0, algorithm=NSEC3Hash.SHA1
)
ce_nsec3_match
), f"Expected matching NSEC3 for {ce} (hash={ce_hash}) not found:\n {response}"
- # Response has NSEC3 that covers the next closer name.
- check_nsec3_covers(nce, response)
-
- wc = dns.name.from_text("*", ce)
- if response.rcode() is dns.rcode.NOERROR:
- # only NOERRORs should be from wildcards
- found_wc = False
- for wildcard in ZONE.reachable_wildcards:
- if wildcard == wc:
- found_wc = True
- assert found_wc
-
- if response.rcode() == dns.rcode.NXDOMAIN:
- # Response has NSEC3 that covers the wildcard.
- check_nsec3_covers(wc, response)
+ # Response has NSEC3 that covers the wildcard
+ check_nsec3_covers(wname, response)
- have NS RR on it, are not zone's apex, and are not occluded
- reachable_dnames - have DNAME RR on it and are not occluded
- reachable_wildcards - have leftmost label '*' and are not occluded
+ - reachable_wildcard_parents - reachable_wildcards with leftmost '*' stripped
Warnings:
- Quadratic complexity ahead! Use only on small test zones.
self.ents = self.generate_ents()
self.reachable_dnames = self.dnames.intersection(self.reachable)
self.reachable_wildcards = self.wildcards.intersection(self.reachable)
+ self.reachable_wildcard_parents = {
+ Name(wname[1:]) for wname in self.reachable_wildcards
+ }
+
+ # (except for wildcard expansions) all names in zone which result in NOERROR answers
+ self.all_existing_names = (
+ self.reachable.union(self.ents)
+ .union(self.reachable_delegations)
+ .union(self.reachable_dnames)
+ )
def get_names_with_type(self, rdtype) -> FrozenSet[Name]:
return frozenset(
return frozenset(ents)
+ def closest_encloser(self, qname: Name):
+ """
+ Get (closest encloser, next closer name) for given qname.
+ """
+ ce = None # Closest encloser, RFC 4592
+ nce = None # Next closer name, RFC 5155
+ for zname in self.all_existing_names:
+ relation, _, common_labels = qname.fullcompare(zname)
+ if relation == NameRelation.SUBDOMAIN:
+ if not ce or common_labels > len(ce):
+ # longest match so far
+ ce = zname
+ _, nce = qname.split(len(ce) + 1)
+ assert ce is not None
+ assert nce is not None
+ return ce, nce
+
+ def source_of_synthesis(self, qname: Name) -> Name:
+ """
+ Return source of synthesis according to RFC 4592 section 3.3.1.
+ Name is not guaranteed to exist or be reachable.
+ """
+ ce, _ = self.closest_encloser(qname)
+ return Name("*") + ce
+
def is_related_to_any(
test_name: Name,
# set of properies present in the tested zone - read by tests_zone_analyzer.py
CATEGORIES = frozenset(
[
+ "all_existing_names",
"delegations",
"dnames",
"ents",
"reachable_delegations",
"reachable_dnames",
"reachable_wildcards",
+ "reachable_wildcard_parents",
"wildcards",
]
)
tags.add("occluded")
if "occluded" not in tags:
+ tags.add("all_existing_names")
if "delegations" in tags:
# delegations are ambiguous and don't count as 'reachable'
tags.add("reachable_delegations")
except ValueError:
break
entname = Name(name[entidx:])
- new_ents[entname] = {"ents"}
+ new_ents[entname] = {"all_existing_names", "ents"}
entidx += 1
return new_ents
+def tag_wildcard_parents(nodes):
+ """
+ Non-occluded nodes with '*' as a leftmost label tag their immediate parent
+ nodes as 'reachable_wildcard_parents'.
+ """
+ for name, tags in nodes.items():
+ if "occluded" in tags or not name.is_wild():
+ continue
+
+ parent_name = Name(name[1:])
+ nodes[parent_name].add("reachable_wildcard_parents")
+
+
def is_non_ent(labels):
"""
Filter out nodes with 'ent' at leftmost position. To become ENT a name must
for labelseq in filter(is_non_ent, itertools.product(LABELS, repeat=length)):
gen_node(nodes, labelseq)
- nodes.update(add_ents(nodes))
-
# special-case to make this look as a valid DNS zone - it needs zone origin node
- nodes[Name([])] = {"reachable"}
+ nodes[Name([])] = {"all_existing_names", "reachable"}
+
+ nodes.update(add_ents(nodes))
+ tag_wildcard_parents(nodes)
with open("analyzer.db", "w", encoding="ascii") as outf:
outf.writelines(gen_zone(nodes))