dnscontrol/pkg/spflib/flatten_test.go
Michael Russell f21c8fc400
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
2020-07-31 13:28:13 -04:00

200 lines
11 KiB
Go

package spflib
import (
"reflect"
"strconv"
"strings"
"testing"
)
func TestFlatten(t *testing.T) {
res, err := NewCache("testdata-dns1.json")
if err != nil {
t.Fatal(err)
}
rec, err := Parse(strings.Join([]string{"v=spf1",
"ip4:198.252.206.0/24",
"ip4:192.111.0.0/24",
"include:_spf.google.com",
"include:mailgun.org",
"include:spf-basic.fogcreek.com",
"include:mail.zendesk.com",
"include:servers.mcsv.net",
"include:sendgrid.net",
"include:spf.mtasv.net",
"~all"}, " "), res)
if err != nil {
t.Fatal(err)
}
t.Log(rec.Print())
rec = rec.Flatten("mailgun.org")
t.Log(rec.Print())
}
// each test is array of strings.
// first item is unsplit input
// next is @ spf record
// after that is alternating record fqdn and value
var splitTests = [][]string{
{
"simple",
"v=spf1 -all",
"v=spf1 -all",
},
{
"longsimple",
"v=spf1 include:a01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.com -all",
"v=spf1 include:a01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.com -all",
},
{
"long simple multipart",
"v=spf1 include:a.com include:b.com include:12345678901234567890123456789000000000000000123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.com -all",
"v=spf1 include:a.com include:b.com include:12345678901234567890123456789000000000000000123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.com -all",
},
{
"overflow",
"v=spf1 include:a.com include:b.com include:X12345678901234567890123456789000000000000000123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.com -all",
"v=spf1 include:a.com include:b.com include:_spf1.stackex.com -all",
"_spf1.stackex.com",
"v=spf1 include:X12345678901234567890123456789000000000000000123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.com -all",
},
{
"overflow all sign carries",
"v=spf1 include:a.com include:b.com include:X12345678901234567890123456789000000000000000123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.com ~all",
"v=spf1 include:a.com include:b.com include:_spf1.stackex.com ~all",
"_spf1.stackex.com",
"v=spf1 include:X12345678901234567890123456789000000000000000123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.com ~all",
},
{
"overhead-200",
"v=spf1 include:a.com include:b.com include:X12345678901234567890123456789000000000000000123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.com ~all",
"v=spf1 include:a.com include:_spf1.stackex.com ~all",
"_spf1.stackex.com",
"v=spf1 include:b.com include:X12345678901234567890123456789000000000000000123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.com ~all",
},
{
"really big",
"v=spf1 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178" +
" ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178" +
" ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 -all",
"v=spf1 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 include:_spf1.stackex.com -all",
"_spf1.stackex.com",
"v=spf1 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 include:_spf2.stackex.com -all",
"_spf2.stackex.com",
"v=spf1 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 include:_spf3.stackex.com -all",
"_spf3.stackex.com",
"v=spf1 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 -all",
},
{
"too long to split",
"v=spf1 include:a0123456789012345678901234567890123456789012345sssss6789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.com -all",
"v=spf1 include:a0123456789012345678901234567890123456789012345sssss6789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.com -all",
},
}
func TestSplit(t *testing.T) {
for _, tst := range splitTests {
overhead := 0
txtMaxSize := 255
v := strings.Split(tst[0], "-")
if len(v) > 1 {
if i, err := strconv.Atoi(v[1]); err == nil {
overhead = i
}
}
t.Run(tst[0], func(t *testing.T) {
rec, err := Parse(tst[1], nil)
if err != nil {
t.Fatal(err)
}
res := rec.TXTSplit("_spf%d.stackex.com", overhead, txtMaxSize)
if res["@"][0] != tst[2] {
t.Fatalf("Root record wrong. \nExp %s\ngot %s", tst[2], res["@"][0])
}
for i := 3; i < len(tst); i += 2 {
fqdn := tst[i]
exp := tst[i+1]
if res[fqdn][0] != exp {
t.Fatalf("Record %s.\nExp %s\ngot %s", fqdn, exp, res[fqdn])
}
}
})
}
}
func TestMultiStringSplit(t *testing.T) {
var tests = []struct {
description string
unsplitInput string
txtMaxSize int
want map[string][]string
}{
{
description: "basic multiple strings",
unsplitInput: "v=spf1 -all",
txtMaxSize: 255,
want: map[string][]string{
"@": {
"v=spf1 -all",
},
},
},
{
description: "too long to split, not enough txtMaxSize for a multi string",
unsplitInput: "v=spf1 include:a0123456789012345678901234567890123456789012345sssss6789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.com -all",
txtMaxSize: 255,
want: map[string][]string{
"@": {
"v=spf1 include:a0123456789012345678901234567890123456789012345sssss6789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.com -all",
},
},
},
{
description: "can actually be split with multiple txt strings",
unsplitInput: "v=spf1 include:a0123456789012345678901234567890123456789012345sssss6789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.com -all",
txtMaxSize: 450,
want: map[string][]string{
"@": {
// First string
"v=spf1 include:a0123456789012345678901234567890123456789012345sssss6789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.com",
// Second string
" -all",
},
},
},
{
description: "really big with multiple txt strings",
unsplitInput: "v=spf1 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178" +
" ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178" +
" ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 -all",
txtMaxSize: 450,
want: map[string][]string{
"@": {
"v=spf1 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.",
"192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 include:_spf1.stackex.com -all",
},
"_spf1.stackex.com": {
"v=spf1 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.",
"192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 ip4:200.192.169.178 -all",
},
},
},
}
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
rec, err := Parse(test.unsplitInput, nil)
if err != nil {
t.Fatal(err)
}
got := rec.TXTSplit("_spf%d.stackex.com", 0, test.txtMaxSize)
if !reflect.DeepEqual(got, test.want) {
t.Errorf("got %v want %v", got, test.want)
}
})
}
}