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

BIND Provider: Pick default TTL automatically for zonefiles (#109)

* BIND Provider: Pick default TTL automatically for zonefiles
This commit is contained in:
Tom Limoncelli
2017-04-30 09:28:35 -04:00
committed by GitHub
parent c13d83887c
commit d92fd5b461
4 changed files with 106 additions and 9 deletions

View File

@@ -70,7 +70,7 @@ func pretty(zonename string, filename string, r io.Reader, defaultTTL uint32) {
l = append(l, x.RR) l = append(l, x.RR)
} }
} }
bind.WriteZoneFile(os.Stdout, l, zonename, defaultTTL) bind.WriteZoneFile(os.Stdout, l, zonename)
} }
// rrFormat outputs the zonefile in either DSL or TSV format. // rrFormat outputs the zonefile in either DSL or TSV format.

View File

@@ -238,7 +238,7 @@ func (c *Bind) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correcti
for _, r := range dc.Records { for _, r := range dc.Records {
zonefilerecords = append(zonefilerecords, r.RR()) zonefilerecords = append(zonefilerecords, r.RR())
} }
err = WriteZoneFile(zf, zonefilerecords, dc.Name, models.DefaultTTL) err = WriteZoneFile(zf, zonefilerecords, dc.Name)
if err != nil { if err != nil {
log.Fatalf("WriteZoneFile error: %v\n", err) log.Fatalf("WriteZoneFile error: %v\n", err)

View File

@@ -57,8 +57,38 @@ func (z *zoneGenData) Less(i, j int) bool {
return a.String() < b.String() return a.String() < b.String()
} }
// mostCommonTtl returns the most common TTL in a set of records. If there is
// a tie, the highest TTL is selected. This makes the results consistent.
// NS records are not included in the analysis because Tom said so.
func mostCommonTtl(records []dns.RR) uint32 {
// Index the TTLs in use:
d := make(map[uint32]int)
for _, r := range records {
if r.Header().Rrtype != dns.TypeNS {
d[r.Header().Ttl]++
}
}
// Find the largest count:
var mc int
for _, value := range d {
if value > mc {
mc = value
}
}
// Find the largest key with that count:
var mk uint32
for key, value := range d {
if value == mc {
if key > mk {
mk = key
}
}
}
return mk
}
// WriteZoneFile writes a beautifully formatted zone file. // WriteZoneFile writes a beautifully formatted zone file.
func WriteZoneFile(w io.Writer, records []dns.RR, origin string, defaultTtl uint32) error { func WriteZoneFile(w io.Writer, records []dns.RR, origin string) error {
// This function prioritizes beauty over efficiency. // This function prioritizes beauty over efficiency.
// * The zone records are sorted by label, grouped by subzones to // * The zone records are sorted by label, grouped by subzones to
// be easy to read and pleasant to the eye. // be easy to read and pleasant to the eye.
@@ -66,9 +96,11 @@ func WriteZoneFile(w io.Writer, records []dns.RR, origin string, defaultTtl uint
// * MX records are sorted numericly by preference value. // * MX records are sorted numericly by preference value.
// * A records are sorted by IP address, not lexicographically. // * A records are sorted by IP address, not lexicographically.
// * Repeated labels are removed. // * Repeated labels are removed.
// * $TTL is used to eliminate clutter. // * $TTL is used to eliminate clutter. The most common TTL value is used.
// * "@" is used instead of the apex domain name. // * "@" is used instead of the apex domain name.
defaultTtl := mostCommonTtl(records)
z := &zoneGenData{ z := &zoneGenData{
Origin: origin, Origin: origin,
DefaultTtl: defaultTtl, DefaultTtl: defaultTtl,

View File

@@ -27,7 +27,7 @@ func parseAndRegen(t *testing.T, buf *bytes.Buffer, expected string) {
} }
// Generate it back: // Generate it back:
buf2 := &bytes.Buffer{} buf2 := &bytes.Buffer{}
WriteZoneFile(buf2, parsed, "bosun.org.", 300) WriteZoneFile(buf2, parsed, "bosun.org.")
// Compare: // Compare:
if buf2.String() != expected { if buf2.String() != expected {
@@ -35,6 +35,49 @@ func parseAndRegen(t *testing.T, buf *bytes.Buffer, expected string) {
} }
} }
func TestMostCommonTtl(t *testing.T) {
var records []dns.RR
var g, e uint32
r1, _ := dns.NewRR("bosun.org. 100 IN A 1.1.1.1")
r2, _ := dns.NewRR("bosun.org. 200 IN A 1.1.1.1")
r3, _ := dns.NewRR("bosun.org. 300 IN A 1.1.1.1")
r4, _ := dns.NewRR("bosun.org. 400 IN NS foo.bosun.org.")
r5, _ := dns.NewRR("bosun.org. 400 IN NS bar.bosun.org.")
// All records are TTL=100
records = nil
records, e = append(records, r1, r1, r1), 100
g = mostCommonTtl(records)
if e != g {
t.Fatalf("expected %d; got %d\n", e, g)
}
// Mixture of TTLs with an obvious winner.
records = nil
records, e = append(records, r1, r2, r2), 200
g = mostCommonTtl(records)
if e != g {
t.Fatalf("expected %d; got %d\n", e, g)
}
// 3-way tie. Largest TTL should be used.
records = nil
records, e = append(records, r1, r2, r3), 300
g = mostCommonTtl(records)
if e != g {
t.Fatalf("expected %d; got %d\n", e, g)
}
// NS records are ignored.
records = nil
records, e = append(records, r1, r4, r5), 100
g = mostCommonTtl(records)
if e != g {
t.Fatalf("expected %d; got %d\n", e, g)
}
}
// func WriteZoneFile // func WriteZoneFile
func TestWriteZoneFileSimple(t *testing.T) { func TestWriteZoneFileSimple(t *testing.T) {
@@ -42,7 +85,7 @@ func TestWriteZoneFileSimple(t *testing.T) {
r2, _ := dns.NewRR("bosun.org. 300 IN A 192.30.252.154") r2, _ := dns.NewRR("bosun.org. 300 IN A 192.30.252.154")
r3, _ := dns.NewRR("www.bosun.org. 300 IN CNAME bosun.org.") r3, _ := dns.NewRR("www.bosun.org. 300 IN CNAME bosun.org.")
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
WriteZoneFile(buf, []dns.RR{r1, r2, r3}, "bosun.org.", 300) WriteZoneFile(buf, []dns.RR{r1, r2, r3}, "bosun.org.")
expected := `$TTL 300 expected := `$TTL 300
@ IN A 192.30.252.153 @ IN A 192.30.252.153
IN A 192.30.252.154 IN A 192.30.252.154
@@ -57,6 +100,28 @@ www IN CNAME bosun.org.
parseAndRegen(t, buf, expected) parseAndRegen(t, buf, expected)
} }
func TestWriteZoneFileSimpleTtl(t *testing.T) {
r1, _ := dns.NewRR("bosun.org. 100 IN A 192.30.252.153")
r2, _ := dns.NewRR("bosun.org. 100 IN A 192.30.252.154")
r3, _ := dns.NewRR("bosun.org. 100 IN A 192.30.252.155")
r4, _ := dns.NewRR("www.bosun.org. 300 IN CNAME bosun.org.")
buf := &bytes.Buffer{}
WriteZoneFile(buf, []dns.RR{r1, r2, r3, r4}, "bosun.org.")
expected := `$TTL 100
@ IN A 192.30.252.153
IN A 192.30.252.154
IN A 192.30.252.155
www 300 IN CNAME bosun.org.
`
if buf.String() != expected {
t.Log(buf.String())
t.Log(expected)
t.Fatalf("Zone file does not match.")
}
parseAndRegen(t, buf, expected)
}
func TestWriteZoneFileMx(t *testing.T) { func TestWriteZoneFileMx(t *testing.T) {
//exhibits explicit ttls and long name //exhibits explicit ttls and long name
r1, _ := dns.NewRR(`bosun.org. 300 IN TXT "aaa"`) r1, _ := dns.NewRR(`bosun.org. 300 IN TXT "aaa"`)
@@ -70,7 +135,7 @@ func TestWriteZoneFileMx(t *testing.T) {
r8, _ := dns.NewRR(`_domainkey.bosun.org. 300 IN TXT "vvvv"`) r8, _ := dns.NewRR(`_domainkey.bosun.org. 300 IN TXT "vvvv"`)
r9, _ := dns.NewRR(`google._domainkey.bosun.org. 300 IN TXT "\"foo\""`) r9, _ := dns.NewRR(`google._domainkey.bosun.org. 300 IN TXT "\"foo\""`)
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
WriteZoneFile(buf, []dns.RR{r1, r2, r3, r4, r5, r6, r7, r8, r9}, "bosun.org", 300) WriteZoneFile(buf, []dns.RR{r1, r2, r3, r4, r5, r6, r7, r8, r9}, "bosun.org")
if buf.String() != testdataZFMX { if buf.String() != testdataZFMX {
t.Log(buf.String()) t.Log(buf.String())
t.Log(testdataZFMX) t.Log(testdataZFMX)
@@ -117,7 +182,7 @@ func TestWriteZoneFileOrder(t *testing.T) {
} }
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
WriteZoneFile(buf, records, "stackoverflow.com.", 300) WriteZoneFile(buf, records, "stackoverflow.com.")
// Compare // Compare
if buf.String() != testdataOrder { if buf.String() != testdataOrder {
t.Log("Found:") t.Log("Found:")
@@ -138,7 +203,7 @@ func TestWriteZoneFileOrder(t *testing.T) {
} }
// Generate // Generate
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
WriteZoneFile(buf, records, "stackoverflow.com.", 300) WriteZoneFile(buf, records, "stackoverflow.com.")
// Compare // Compare
if buf.String() != testdataOrder { if buf.String() != testdataOrder {
t.Log(buf.String()) t.Log(buf.String())