Zone13
Status: Final
Purpose
- Validate that the SPF policy at the zone apex does not exceed the DNS lookup limit defined in RFC 7208 Section 4.6.4.
- Detect use of the deprecated
ptrmechanism (RFC 7208 Section 5.5).
Preconditions And Inputs
- Preconditions:
- A
zone.Zoneobject is available. - The module runner starts Zone13 when Zone11 accepted the apex policy, that is when any of
Z11_SPF_SYNTAX_OK,Z11_NULL_SPF_NON_MAIL_DOMAINorZ11_NON_NULL_SPF_NON_MAIL_DOMAINwas emitted, or when Zone11 was not selected for the run. Zone13 is skipped, together with Zone10 to Zone12, whenNO_RESPONSE_SOA_QUERYwas raised. - Zone13 itself inspects no prior results and retrieves the policy independently.
- A
- Required inputs:
- The SPF TXT record at the zone apex.
- Live DNS resolution for following
include/redirectchains.
- Profile/config knobs that affect behavior:
test_cases_vars.zone13.spf_lookup_limit: maximum allowed DNS-resolving mechanisms (default:10per RFC 7208).net.ipv4andnet.ipv6: disabled transports are skipped.resolver.defaults.parallel: parallel nameserver query fanout.
Algorithm And Decision Flow
- Emit
TEST_CASE_START. - Retrieve the SPF TXT record from the zone apex (query authoritative nameserver).
- If no authoritative response is available, emit
Z13_UNABLE_TO_CHECKand stop. - If the response carries no
v=spf1record, emitZ13_NO_SPF_FOUNDand stop.
- If no authoritative response is available, emit
- Parse the SPF record into terms.
- Walk the terms recursively, maintaining a visited-domain set for loop detection:
- For each term, classify by mechanism type:
include:domain: count +1, recursively fetch and walk the target domain’s SPF record.redirect=domain: count +1, recursively fetch and walk the target domain’s SPF record.a,a:domain,a:domain/cidr: count +1.mx,mx:domain,mx:domain/cidr: count +1.ptr,ptr:domain: count +1, also flag as deprecated.exists:domain: count +1.all,ip4:...,ip6:...: do not count (no DNS lookup).exp=domain: do not count toward the mechanism limit.
- If an
include/redirecttarget contains an SPF macro (RFC 7208 Section 7), the count is still incremented but the target is not followed; emitZ13_SPF_MACRO_TARGET. The sub-tree’s lookup count is undecidable at audit time. - If a domain has already been visited during recursion, emit
Z13_SPF_LOOKUP_LOOPand stop recursing that branch. - If an
include/redirecttarget cannot be resolved, emitZ13_SPF_RECURSIVE_ERRORand stop recursing that branch. - If a
ptrorptr:domainmechanism is encountered, emitZ13_SPF_PTR_DEPRECATED.
- For each term, classify by mechanism type:
- Compare the total count against the configured
spf_lookup_limit:- If count <= limit, emit
Z13_SPF_LOOKUP_COUNT_OK. - If count > limit, emit
Z13_SPF_LOOKUP_COUNT_EXCEEDED.
- If count <= limit, emit
- Emit
TEST_CASE_END.
SPF Lookup Walk and Limit Check (steps 2-6)
Emitted Tags (Possible Set)
| Tag | Emitted when |
|---|---|
Z13_NO_SPF_FOUND | The authoritative apex TXT response carried no v=spf1 record. |
Z13_SPF_LOOKUP_COUNT_EXCEEDED | Total DNS-resolving mechanism count exceeds the configured limit. |
Z13_SPF_LOOKUP_COUNT_OK | Total DNS-resolving mechanism count is within the configured limit. |
Z13_SPF_LOOKUP_LOOP | Recursive include/redirect chain revisits a previously seen domain. |
Z13_SPF_MACRO_TARGET | An include/redirect target contains SPF macros and cannot be followed at audit time. |
Z13_SPF_PTR_DEPRECATED | SPF record uses the deprecated ptr mechanism (RFC 7208 Section 5.5). |
Z13_SPF_RECURSIVE_ERROR | An include/redirect target could not be resolved via DNS. |
Z13_UNABLE_TO_CHECK | No authoritative TXT response could be obtained for the zone apex. |
TEST_CASE_END | Testcase completion marker is emitted. |
TEST_CASE_START | Testcase start marker is emitted. |
Tag Arguments
| Tag | Argument key | Type | Meaning |
|---|---|---|---|
Z13_NO_SPF_FOUND | domain | string | Tested zone name. |
Z13_SPF_LOOKUP_COUNT_EXCEEDED | domain | string | Tested zone name. |
Z13_SPF_LOOKUP_COUNT_EXCEEDED | count | int | Total number of DNS-resolving mechanisms found. |
Z13_SPF_LOOKUP_COUNT_EXCEEDED | limit | int | Configured lookup limit. |
Z13_SPF_LOOKUP_COUNT_OK | domain | string | Tested zone name. |
Z13_SPF_LOOKUP_COUNT_OK | count | int | Total number of DNS-resolving mechanisms found. |
Z13_SPF_LOOKUP_LOOP | domain | string | Tested zone name. |
Z13_SPF_LOOKUP_LOOP | loop_domain | string | The domain that was visited a second time. |
Z13_SPF_MACRO_TARGET | domain | string | Tested zone name. |
Z13_SPF_MACRO_TARGET | target | string | The macro-laden include/redirect target as published in the SPF record. |
Z13_SPF_PTR_DEPRECATED | domain | string | Tested zone name. |
Z13_SPF_RECURSIVE_ERROR | domain | string | Tested zone name. |
Z13_SPF_RECURSIVE_ERROR | target | string | The include/redirect target domain that could not be resolved. |
Z13_UNABLE_TO_CHECK | - | - | No arguments. |
TEST_CASE_END | testcase | string | Testcase display name (Zone13). |
TEST_CASE_START | testcase | string | Testcase display name (Zone13). |
Severity Levels Per Tag
| Tag | Level | Notes |
|---|---|---|
Z13_NO_SPF_FOUND | INFO | Default from share/profile.json (test_levels.ZONE). |
Z13_SPF_LOOKUP_COUNT_EXCEEDED | WARNING | Default from share/profile.json (test_levels.ZONE). |
Z13_SPF_LOOKUP_COUNT_OK | INFO | Default from share/profile.json (test_levels.ZONE). |
Z13_SPF_LOOKUP_LOOP | WARNING | Default from share/profile.json (test_levels.ZONE). |
Z13_SPF_MACRO_TARGET | NOTICE | Default from share/profile.json (test_levels.ZONE). |
Z13_SPF_PTR_DEPRECATED | WARNING | Default from share/profile.json (test_levels.ZONE). |
Z13_SPF_RECURSIVE_ERROR | NOTICE | Default from share/profile.json (test_levels.ZONE). |
Z13_UNABLE_TO_CHECK | WARNING | Default from share/profile.json (test_levels.ZONE). |
TEST_CASE_END | DEBUG | Default from share/profile.json (test_levels.ZONE). |
TEST_CASE_START | DEBUG | Default from share/profile.json (test_levels.ZONE). |
Differences From Upstream
- No upstream (Zonemaster) equivalent exists; this is a gonemaster-specific test case.
- References: RFC 7208 Section 4.6.4 , RFC 7208 Section 5.5
Edge Cases And Limitations
- When Zone11 ran and rejected the apex policy, or found none, the runner does not start Zone13 and no Zone13 tag is emitted.
- When Zone11 was not selected, Zone13 walks the retrieved policy without syntax validation, because the walk ignores terms it does not recognise. A record Zone11 would have rejected can therefore still yield a lookup count.
- Root, TLD and
.arpazones are analysed whenever Zone11 accepted the published policy. RFC 7208 Section 4.6.4 exempts no zone class. - The test performs live DNS lookups to follow
include/redirectchains. Results may vary depending on network conditions and the state of external DNS records. - Loop detection prevents infinite recursion but the count up to the loop detection point is still included in the total.
exp=domainmodifiers are not counted toward the lookup limit per RFC 7208, as they are only evaluated during result explanation and do not affect SPF evaluation.- The
ptrmechanism is counted toward the lookup limit AND flagged as deprecated; both tags may be emitted for the same record. - Qualified mechanisms (e.g.,
+include:,-a,~mx) are handled identically to their unqualified forms for counting purposes. - SPF macros (RFC 7208 Section 7) are detected by the presence of
%in aninclude/redirecttarget. Such targets are not resolved because macros (e.g.,%{ir},%{v},%{d}) are only expanded at SMTP time with a real client IP/sender; the audit emitsZ13_SPF_MACRO_TARGETand stops recursing that branch. The+1lookup itself still counts toward the limit, but any nested lookups in the macro-targeted policy are not counted.