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:
@ -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.
|
||||
|
Reference in New Issue
Block a user