Section 10.1, "Processing Limits" of the SPF RFC 4408 specifies the following in regards to DNS lookups:
SPF implementations MUST limit the number of mechanisms and modifiers that do DNS lookups to at most 10 per SPF check, including any lookups caused by the use of the "include" mechanism or the "redirect" modifier. If this number is exceeded during a check, a PermError MUST be returned. The "include", "a", "mx", "ptr", and "exists" mechanisms as well as the "redirect" modifier do count against this limit. The "all", "ip4", and "ip6" mechanisms do not require DNS lookups and therefore do not count against this limit. The "exp" modifier does not count against this limit because the DNS lookup to fetch the explanation string occurs after the SPF record has been evaluated.
This limit is in place to prevent SPF lookups from being a useful avenue for Denial of Service attacks.
Using an example SPF record as an example to illustrate, this record was breaking with 12 look-ups:
example.com text = "v=spf1 include:_spf-a.example.com include:_spf-b.example.com include:_spf-c.example.com include:_spf-ssg-a.example.com include:spf-a.anotherexample.com ip4:184.108.40.206 ip4:220.127.116.11 ip4:18.104.22.168 ip4:22.214.171.124 ip4:126.96.36.199 ~all" [ 5 mechanisms]
_spf-a.example.com text = "v=spf1 ip4:188.8.131.52 ip4:184.108.40.206 ip4:220.127.116.11 ip4:18.104.22.168 ip4:22.214.171.124 ip4:126.96.36.199 ip4:188.8.131.52 ip4:184.108.40.206 ip4:220.127.116.11 a:mh.example.m0.net ~all" [ +1 = 6 mechanisms]
mh.example.m0.net a = 18.104.22.168
_spf-b.example.com text = "v=spf1 include:spf.messaging.example.com ip4:22.214.171.124 ip4:126.96.36.199 ip4:188.8.131.52 ip4:184.108.40.206 ip4:220.127.116.11 ip4:18.104.22.168 ip4:22.214.171.124 ip4:126.96.36.199 ip4:188.8.131.52 ip4:184.108.40.206 ip4:220.127.116.11 ~all" [+1 = 7 mechanisms]
spf.messaging.example.com text = "v=spf1 include:spfa.anotherexample.com include:spfb.anotherexaple.com include:spfc.anotherexample.com -all" [+3 = 10 mechanisms]
spfa.anotherexample.com text = "v=spf1 ip4:18.104.22.168/26 ip4:22.214.171.124/24 ip4:126.96.36.199/23 ip4:188.8.131.52/24 ip4:184.108.40.206/24 ip4:220.127.116.11/25 ip4:18.104.22.168/25 ip4:22.214.171.124/24 ip4:126.96.36.199/25 ip4:188.8.131.52/24 ip4:184.108.40.206/24 ip4:220.127.116.11/23 -all" [+0 = 10 mechanisms]
spfb.anotherexample.com text = "v=spf1 ip4:18.104.22.168/26 ip4:22.214.171.124/24 ip4:126.96.36.199/26 ip4:188.8.131.52/23 ip4:184.108.40.206/26 ip4:220.127.116.11/27 ip4:18.104.22.168/24 ip4:22.214.171.124/24 ip4:126.96.36.199/26 ip4:188.8.131.52/16 ip4:184.108.40.206/24 ip4:220.127.116.11/24 -all" [+0 = 10 mechanisms]
spfc.anotherexample.com text = "v=spf1 ip4:18.104.22.168/26 ip6:2a01:111:f400:7c00::/54 ip6:2a01:111:f400:fc00::/54 ip4:22.214.171.124/26 ip4:126.96.36.199/27 ip4:188.8.131.52/27 ip4:184.108.40.206/27 ip4:220.127.116.11/24 ip4:18.104.22.168/24 ip4:22.214.171.124/23 ip4:126.96.36.199/22 -all" [ +0 = 10 mechanisms]
_spf-c.example.com text = "v=spf1 ip4:188.8.131.52 ip4:184.108.40.206 ip4:220.127.116.11 ip4:18.104.22.168 ip4:22.214.171.124 ip4:126.96.36.199 ip4:188.8.131.52 ip4:184.108.40.206 ip4:220.127.116.11 ip4:18.104.22.168 ip4:22.214.171.124 ip4:126.96.36.199 ip4:188.8.131.52 ~all" [+0 = 10 mechanisms]
_spf-ssg-a.example.com text = "v=spf1 include:_spf-ssg-b.example.com include:_spf-ssg-c.example.com ~all" [+2 = 12 mechanisms]
_spf-ssg-b.example.com text = "v=spf1 ip4:184.108.40.206/30 ip4:220.127.116.11/26 ip4:18.104.22.168/27 ip4:22.214.171.124/27 ip4:126.96.36.199/26 ip4:188.8.131.52/26 ip4:184.108.40.206/29 ip4:220.127.116.11/27 ip4:18.104.22.168/27 ip4:22.214.171.124/28 ~all" [+0 = 12 mechanisms]
_spf-ssg-c.example.com text = "v=spf1 ip4:126.96.36.199/29 ip4:188.8.131.52/28 ip4:184.108.40.206/26 ip4:220.127.116.11/27 ip4:18.104.22.168/27 ip4:22.214.171.124/31 ip4:126.96.36.199/27 ip4:188.8.131.52/27 ip4:184.108.40.206/26 ip4:220.127.116.11 ~all" [+0 = 12 mechanisms]
spf-a.secondexample.com text = "v=spf1 ip4:18.104.22.168/26 ip4:22.214.171.124/26 ip4:126.96.36.199/25 ip4:188.8.131.52/24 ip4:184.108.40.206/26 ip4:220.127.116.11/26 ip4:18.104.22.168/24 ip4:22.214.171.124/25 ip4:126.96.36.199/24 ip4:188.8.131.52/24 ip4:184.108.40.206/24 ip4:220.127.116.11/24 ~all" [+0 = 12 mechanisms]
255 character limitation in a single string
You may have more than 255 characters of data in a TXT or SPF record, but not more than 255 characters in a single string.If you attempt to create an SPF or TXT record with a long string (>255 characters) in it, BIND will give an error (e.g. "invalid rdata format: ran out of space".) Strings in SPF and TXT records should be no longer than 255 characters. However to get around this limitation, per RFC 4408 a TXT or SPF record is allowed to contain multiple strings, which should be concatenated together by the reading application. In the case of use for SPF (using either TXT or SPF RRs) the strings are concatenated together without spaces as described below. Reassembly by other applications of multiple strings stored in TXT records might work differently.
3.1.3. Multiple Strings in a Single DNS record
As defined in [RFC1035] sections 3.3.14 and 3.3, a single text DNS record (either TXT or SPF RR types) can be composed of more than one string. If a published record contains multiple strings, then the record MUST be treated as if those strings are concatenated together without adding spaces. For example: IN TXT "v=spf1 .... first" "second string..." MUST be treated as equivalent to IN TXT "v=spf1 .... firstsecond string..." SPF or TXT records containing multiple strings are useful in constructing records that would exceed the 255-byte maximum length of a string within a single TXT or SPF RR record.
text = "v=spf1 ip4:18.104.22.168/22 ip4:22.214.171.124/24 ip4:126.96.36.199/24 ip4:188.8.131.52/24 ip4:184.108.40.206/24 ip4:220.127.116.11/26 ip4:18.104.22.168/32 ip4:22.214.171.124/22 ~all“
text = "v=spf1 ip4:126.96.36.199/22“ " ip4:188.8.131.52/24 ip4:184.108.40.206/24 ip4:220.127.116.11/24" " ip4:18.104.22.168/24 ip4:22.214.171.124/26" " ip4:126.96.36.199/32 ip4:188.8.131.52/22 ~all"
A record that is NULL or that does not exist will break an SPF record. Syntax within the record is very important, if there are extra spaces between mechanisms it will count as NULL.
text = "v=spf1 ip4:184.108.40.206/22“ <- accurate
text = "v=spf1 ip4: 220.127.116.11/22“ <- NULL (NOTE the space between IP4: and the IP)
If there are too many repetitive mechanisms in the SPF record, including records that cascade (for example when using "include:") the record will break.
There is a MAX of 2 void look ups in an SPF record. More than that and the record will break. This prevents SPF records from being used in Denial of Service style attacks.
SPF checker, syntax validator and SPF tester
SPF creation wizard
Common SPF errors
SPF syntax definitions