1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00

SPF Optimizer: Enable the use of TXTMulti records to support longer SPF records (#794)

* Add multiple string support to SPF optimizer

Notes:

* This implements [RFC 4408][rfc] for the SPF optimizer. Allowing for
more SPF records to fit within the 10 lookups by using multiple strings.
* By default the max size of the TXT remains at 255. Meaning users will
still only get a single 255 length string unless they modify `txtMaxSize`
and opt into this feature.
* The general recommendation when using multiple strings for TXT records
is to keep the size within a single UDP packet. It seems like the
maximum size for this depends on a bunch of factors that are sometimes
outside of your control. A similar tool has a [formula for estimating the
maximum allowed size][formula]. However I felt giving a user
configurable size would fit with the current configuration style that
dnscontrol has. Similar to how dnscontrol recommends only flattening a
record if absolutely needed, I can see this length being increased by
only enough to get you within 10 lookups.

[rfc]: https://tools.ietf.org/html/rfc4408#section-3.1.3
[formula]: https://github.com/oasys/mkspf/blob/master/Overhead.md

* Add a nice comment for the Chunks function
This commit is contained in:
Michael Russell
2020-07-31 19:28:13 +02:00
committed by GitHub
parent 237c573c2a
commit f21c8fc400
6 changed files with 245 additions and 119 deletions

View File

@ -5,6 +5,31 @@ import (
"strings"
)
// Chunks splits strings into arrays of a certain chunk size. We
// use this to split TXT records into 255 sized chunks for RFC 4408
// https://tools.ietf.org/html/rfc4408#section-3.1.3
// Borrowed from https://stackoverflow.com/a/61469854/11477663
func Chunks(s string, chunkSize int) []string {
if chunkSize >= len(s) {
return []string{s}
}
var chunks []string
chunk := make([]rune, chunkSize)
len := 0
for _, r := range s {
chunk[len] = r
len++
if len == chunkSize {
chunks = append(chunks, string(chunk))
len = 0
}
}
if len > 0 {
chunks = append(chunks, string(chunk[:len]))
}
return chunks
}
// TXT outputs s as a TXT record.
func (s *SPFRecord) TXT() string {
text := "v=spf1"
@ -14,7 +39,10 @@ func (s *SPFRecord) TXT() string {
return text
}
const maxLenDefault = 255
// Maximum length of a single TXT string. Anything
// bigger than this will be split into multiple strings
// if the user has set a txtMaxSize length greater than 255
const txtStringLength = 255
// TXTSplit returns a set of txt records to use for SPF.
// pattern given is used to name all chained spf records.
@ -26,20 +54,20 @@ const maxLenDefault = 255
// overhead of that many bytes. For example, if there are other txt
// records and you wish to reduce the first SPF record size to prevent
// DNS over TCP.
func (s *SPFRecord) TXTSplit(pattern string, overhead int) map[string]string {
m := map[string]string{}
s.split("@", pattern, 1, m, overhead)
func (s *SPFRecord) TXTSplit(pattern string, overhead int, txtMaxSize int) map[string][]string {
m := map[string][]string{}
s.split("@", pattern, 1, m, overhead, txtMaxSize)
return m
}
func (s *SPFRecord) split(thisfqdn string, pattern string, nextIdx int, m map[string]string, overhead int) {
maxLen := maxLenDefault - overhead
func (s *SPFRecord) split(thisfqdn string, pattern string, nextIdx int, m map[string][]string, overhead int, txtMaxSize int) {
maxLen := txtMaxSize - overhead
base := s.TXT()
// simple case. it fits
if len(base) <= maxLen {
m[thisfqdn] = base
m[thisfqdn] = Chunks(base, txtStringLength)
return
}
@ -62,7 +90,7 @@ func (s *SPFRecord) split(thisfqdn string, pattern string, nextIdx int, m map[st
over = true
if addedCount == 0 {
// the first part is too big to include. We kinda have to give up here.
m[thisfqdn] = base
m[thisfqdn] = []string{base}
return
}
}
@ -71,8 +99,9 @@ func (s *SPFRecord) split(thisfqdn string, pattern string, nextIdx int, m map[st
newRec.Parts = append(newRec.Parts, part)
}
}
m[thisfqdn] = thisText + tail
newRec.split(nextFQDN, pattern, nextIdx+1, m, 0)
m[thisfqdn] = Chunks(thisText+tail, txtStringLength)
newRec.split(nextFQDN, pattern, nextIdx+1, m, 0, txtMaxSize)
}
// Flatten optimizes s.