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 ptr mechanism (RFC 7208 Section 5.5).

Preconditions And Inputs

  • Preconditions:
    • A zone.Zone object 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_DOMAIN or Z11_NON_NULL_SPF_NON_MAIL_DOMAIN was emitted, or when Zone11 was not selected for the run. Zone13 is skipped, together with Zone10 to Zone12, when NO_RESPONSE_SOA_QUERY was raised.
    • Zone13 itself inspects no prior results and retrieves the policy independently.
  • Required inputs:
    • The SPF TXT record at the zone apex.
    • Live DNS resolution for following include/redirect chains.
  • Profile/config knobs that affect behavior:
    • test_cases_vars.zone13.spf_lookup_limit: maximum allowed DNS-resolving mechanisms (default: 10 per RFC 7208).
    • net.ipv4 and net.ipv6: disabled transports are skipped.
    • resolver.defaults.parallel: parallel nameserver query fanout.

Algorithm And Decision Flow

  1. Emit TEST_CASE_START.
  2. Retrieve the SPF TXT record from the zone apex (query authoritative nameserver).
    • If no authoritative response is available, emit Z13_UNABLE_TO_CHECK and stop.
    • If the response carries no v=spf1 record, emit Z13_NO_SPF_FOUND and stop.
  3. Parse the SPF record into terms.
  4. 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/redirect target contains an SPF macro (RFC 7208 Section 7), the count is still incremented but the target is not followed; emit Z13_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_LOOP and stop recursing that branch.
    • If an include/redirect target cannot be resolved, emit Z13_SPF_RECURSIVE_ERROR and stop recursing that branch.
    • If a ptr or ptr:domain mechanism is encountered, emit Z13_SPF_PTR_DEPRECATED.
  5. 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.
  6. Emit TEST_CASE_END.

SPF Lookup Walk and Limit Check (steps 2-6)

retrieve apex SPF TXT record (authoritative query)
   no usable authoritative response
   -> Z13_UNABLE_TO_CHECK (no args)
      emit TEST_CASE_END and stop
   response carries no v=spf1 record
   -> Z13_NO_SPF_FOUND (domain)
      emit TEST_CASE_END and stop

parse SPF record into terms
walk terms recursively, carrying visited-domain set; count = 0:

   per term, by mechanism (qualifier-stripped):
     a | a:domain | a:domain/cidr           -> count += 1
     mx | mx:domain | mx:domain/cidr        -> count += 1
     exists:domain                          -> count += 1
     ptr | ptr:domain                       -> count += 1
                                             Z13_SPF_PTR_DEPRECATED (domain)
     include:domain | redirect=domain       -> count += 1
        target contains "%" (SPF macro)
           -> Z13_SPF_MACRO_TARGET (domain, target)
              do not recurse
        target already in visited set
           -> Z13_SPF_LOOKUP_LOOP (domain, loop_domain)
              do not recurse
        target SPF cannot be resolved
           -> Z13_SPF_RECURSIVE_ERROR (domain, target)
              do not recurse
        otherwise
           -> add to visited; fetch+parse target SPF; recurse
     all | ip4:... | ip6:...                -> (no count)
     exp=domain                             -> (no count)

count <= spf_lookup_limit  -> Z13_SPF_LOOKUP_COUNT_OK       (domain, count)
count >  spf_lookup_limit  -> Z13_SPF_LOOKUP_COUNT_EXCEEDED (domain, count, limit)

emit TEST_CASE_END

Emitted Tags (Possible Set)

TagEmitted when
Z13_NO_SPF_FOUNDThe authoritative apex TXT response carried no v=spf1 record.
Z13_SPF_LOOKUP_COUNT_EXCEEDEDTotal DNS-resolving mechanism count exceeds the configured limit.
Z13_SPF_LOOKUP_COUNT_OKTotal DNS-resolving mechanism count is within the configured limit.
Z13_SPF_LOOKUP_LOOPRecursive include/redirect chain revisits a previously seen domain.
Z13_SPF_MACRO_TARGETAn include/redirect target contains SPF macros and cannot be followed at audit time.
Z13_SPF_PTR_DEPRECATEDSPF record uses the deprecated ptr mechanism (RFC 7208 Section 5.5).
Z13_SPF_RECURSIVE_ERRORAn include/redirect target could not be resolved via DNS.
Z13_UNABLE_TO_CHECKNo authoritative TXT response could be obtained for the zone apex.
TEST_CASE_ENDTestcase completion marker is emitted.
TEST_CASE_STARTTestcase start marker is emitted.

Tag Arguments

TagArgument keyTypeMeaning
Z13_NO_SPF_FOUNDdomainstringTested zone name.
Z13_SPF_LOOKUP_COUNT_EXCEEDEDdomainstringTested zone name.
Z13_SPF_LOOKUP_COUNT_EXCEEDEDcountintTotal number of DNS-resolving mechanisms found.
Z13_SPF_LOOKUP_COUNT_EXCEEDEDlimitintConfigured lookup limit.
Z13_SPF_LOOKUP_COUNT_OKdomainstringTested zone name.
Z13_SPF_LOOKUP_COUNT_OKcountintTotal number of DNS-resolving mechanisms found.
Z13_SPF_LOOKUP_LOOPdomainstringTested zone name.
Z13_SPF_LOOKUP_LOOPloop_domainstringThe domain that was visited a second time.
Z13_SPF_MACRO_TARGETdomainstringTested zone name.
Z13_SPF_MACRO_TARGETtargetstringThe macro-laden include/redirect target as published in the SPF record.
Z13_SPF_PTR_DEPRECATEDdomainstringTested zone name.
Z13_SPF_RECURSIVE_ERRORdomainstringTested zone name.
Z13_SPF_RECURSIVE_ERRORtargetstringThe include/redirect target domain that could not be resolved.
Z13_UNABLE_TO_CHECK--No arguments.
TEST_CASE_ENDtestcasestringTestcase display name (Zone13).
TEST_CASE_STARTtestcasestringTestcase display name (Zone13).

Severity Levels Per Tag

TagLevelNotes
Z13_NO_SPF_FOUNDINFODefault from share/profile.json (test_levels.ZONE).
Z13_SPF_LOOKUP_COUNT_EXCEEDEDWARNINGDefault from share/profile.json (test_levels.ZONE).
Z13_SPF_LOOKUP_COUNT_OKINFODefault from share/profile.json (test_levels.ZONE).
Z13_SPF_LOOKUP_LOOPWARNINGDefault from share/profile.json (test_levels.ZONE).
Z13_SPF_MACRO_TARGETNOTICEDefault from share/profile.json (test_levels.ZONE).
Z13_SPF_PTR_DEPRECATEDWARNINGDefault from share/profile.json (test_levels.ZONE).
Z13_SPF_RECURSIVE_ERRORNOTICEDefault from share/profile.json (test_levels.ZONE).
Z13_UNABLE_TO_CHECKWARNINGDefault from share/profile.json (test_levels.ZONE).
TEST_CASE_ENDDEBUGDefault from share/profile.json (test_levels.ZONE).
TEST_CASE_STARTDEBUGDefault from share/profile.json (test_levels.ZONE).

Differences From Upstream

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 .arpa zones 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/redirect chains. 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=domain modifiers 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 ptr mechanism 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 an include/redirect target. 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 emits Z13_SPF_MACRO_TARGET and stops recursing that branch. The +1 lookup itself still counts toward the limit, but any nested lookups in the macro-targeted policy are not counted.