diff --git a/docs/_providers/bind.md b/docs/_providers/bind.md index a560a44f8..8f51f608e 100644 --- a/docs/_providers/bind.md +++ b/docs/_providers/bind.md @@ -33,4 +33,4 @@ var bind = NewDnsProvider('bind', 'BIND', { If you need to customize your SOA or NS records, you can do it with this setup. -You can also provide a `-bindtree=directoryName` flag to change where the provider will look for and create zone files. The default is the `zones` directory where dnscontrol is run. \ No newline at end of file +You can also provide a `-bindtree=directoryName` flag to change where the provider will look for and create zone files. The default is the `zones` directory where dnscontrol is run. diff --git a/integrationTest/integration_test.go b/integrationTest/integration_test.go index 4e635d3cf..11edc4c66 100644 --- a/integrationTest/integration_test.go +++ b/integrationTest/integration_test.go @@ -119,7 +119,12 @@ func runTests(t *testing.T, prv providers.DNSServiceProvider, domainName string, t.Fatal(err) } if !skipVal && i != *startIdx && len(corrections) == 0 { - t.Fatalf("Expect changes for all tests, but got none") + if tst.Desc != "Empty" { + // There are "no corrections" if the last test was programatically + // skipped. We detect this (possibly inaccurately) by checking to + // see if .Desc is "Empty". + t.Fatalf("Expect changes for all tests, but got none") + } } for _, c := range corrections { if *verbose { @@ -215,7 +220,7 @@ func ns(name, target string) *rec { func mx(name string, prio uint16, target string) *rec { r := makeRec(name, target, "MX") - r.Priority = prio + r.MxPreference = prio return r } @@ -223,6 +228,14 @@ func ptr(name, target string) *rec { return makeRec(name, target, "PTR") } +func srv(name string, priority, weight, port uint16, target string) *rec { + r := makeRec(name, target, "SRV") + r.SrvPriority = priority + r.SrvWeight = weight + r.SrvPort = port + return r +} + func makeRec(name, target, typ string) *rec { return &rec{ Name: name, @@ -257,8 +270,8 @@ var tests = []*TestCase{ tc("Change it", a("@", "1.2.3.4")), tc("Add another", a("@", "1.2.3.4"), a("www", "1.2.3.4")), tc("Add another(same name)", a("@", "1.2.3.4"), a("www", "1.2.3.4"), a("www", "5.6.7.8")), - tc("Change a ttl", a("@", "1.2.3.4").ttl(100), a("www", "1.2.3.4"), a("www", "5.6.7.8")), - tc("Change single target from set", a("@", "1.2.3.4").ttl(100), a("www", "2.2.2.2"), a("www", "5.6.7.8")), + tc("Change a ttl", a("@", "1.2.3.4").ttl(1000), a("www", "1.2.3.4"), a("www", "5.6.7.8")), + tc("Change single target from set", a("@", "1.2.3.4").ttl(1000), a("www", "2.2.2.2"), a("www", "5.6.7.8")), tc("Change all ttls", a("@", "1.2.3.4").ttl(500), a("www", "2.2.2.2").ttl(400), a("www", "5.6.7.8").ttl(400)), tc("Delete one", a("@", "1.2.3.4").ttl(500), a("www", "5.6.7.8").ttl(400)), tc("Add back and change ttl", a("www", "5.6.7.8").ttl(700), a("www", "1.2.3.4").ttl(700)), @@ -290,7 +303,7 @@ var tests = []*TestCase{ tc("3 MX", mx("@", 5, "foo.com."), mx("@", 5, "foo2.com."), mx("@", 15, "foo3.com.")), tc("Delete one", mx("@", 5, "foo2.com."), mx("@", 15, "foo3.com.")), tc("Change to other name", mx("@", 5, "foo2.com."), mx("mail", 15, "foo3.com.")), - tc("Change Priority", mx("@", 7, "foo2.com."), mx("mail", 15, "foo3.com.")), + tc("Change Preference", mx("@", 7, "foo2.com."), mx("mail", 15, "foo3.com.")), //PTR tc("Empty"), @@ -298,11 +311,22 @@ var tests = []*TestCase{ tc("Modify PTR record", ptr("4", "bar.com.")), //ALIAS - tc("EMPTY"), + tc("Empty"), tc("ALIAS at root", alias("@", "foo.com.")).IfHasCapability(providers.CanUseAlias), tc("change it", alias("@", "foo2.com.")).IfHasCapability(providers.CanUseAlias), tc("ALIAS at subdomain", alias("test", "foo.com.")).IfHasCapability(providers.CanUseAlias), + //SRV + tc("Empty"), + tc("SRV record", srv("@", 5, 6, 7, "foo.com.")), + tc("Second SRV record, same prio", srv("@", 5, 6, 7, "foo.com."), srv("@", 5, 60, 70, "foo2.com.")), + tc("3 SRV", srv("@", 5, 6, 7, "foo.com."), srv("@", 5, 60, 70, "foo2.com."), srv("@", 15, 65, 75, "foo3.com.")), + tc("Delete one", srv("@", 5, 6, 7, "foo.com."), srv("@", 15, 65, 75, "foo3.com.")), + tc("Change Target", srv("@", 5, 6, 7, "foo.com."), srv("@", 15, 65, 75, "foo4.com.")), + tc("Change Priority", srv("@", 52, 6, 7, "foo.com."), srv("@", 15, 65, 75, "foo4.com.")), + tc("Change Weight", srv("@", 52, 62, 7, "foo.com."), srv("@", 15, 65, 75, "foo4.com.")), + tc("Change Port", srv("@", 52, 62, 72, "foo.com."), srv("@", 15, 65, 75, "foo4.com.")), + //TODO: in validation, check that everything is given in unicode. This case hurts too much. //tc("IDN pre-punycoded", cname("xn--o-0gab", "xn--o-0gab.xn--o-0gab.")), } diff --git a/integrationTest/providers.json b/integrationTest/providers.json index 35bff3f90..b28ea1148 100644 --- a/integrationTest/providers.json +++ b/integrationTest/providers.json @@ -20,7 +20,7 @@ "token": "$DNSIMPLE_TOKEN" }, "GANDI": { - "COMMENT": "5: gandi does not accept ttls less than 300", + "COMMENT": "5: gandi does not accept TTLs less than 300", "apikey": "$GANDI_KEY", "domain": "$GANDI_DOMAIN", "knownFailures": "5" diff --git a/integrationTest/zones/example.com.zone b/integrationTest/zones/example.com.zone index 808c147dc..e43d3b661 100644 --- a/integrationTest/zones/example.com.zone +++ b/integrationTest/zones/example.com.zone @@ -1,4 +1,4 @@ $TTL 300 -@ IN SOA DEFAULT_NOT_SET. DEFAULT_NOT_SET. 2017070632 3600 600 604800 1440 +@ IN SOA DEFAULT_NOT_SET. DEFAULT_NOT_SET. 2017071945 3600 600 604800 1440 IN NS ns1.otherdomain.tld. IN NS ns2.otherdomain.tld. diff --git a/models/dns.go b/models/dns.go index d99e84d68..42864b700 100644 --- a/models/dns.go +++ b/models/dns.go @@ -9,6 +9,7 @@ import ( "net" "reflect" "strconv" + "strings" "github.com/StackExchange/dnscontrol/pkg/transform" "github.com/miekg/dns" @@ -60,21 +61,30 @@ type DNSProviderConfig struct { // This is the FQDN version of Name. // It should never have a trailiing ".". type RecordConfig struct { - Type string `json:"type"` - Name string `json:"name"` // The short name. See below. - Target string `json:"target"` // If a name, must end with "." - TTL uint32 `json:"ttl,omitempty"` - Metadata map[string]string `json:"meta,omitempty"` - NameFQDN string `json:"-"` // Must end with ".$origin". See below. - Priority uint16 `json:"priority,omitempty"` + Type string `json:"type"` + Name string `json:"name"` // The short name. See below. + Target string `json:"target"` // If a name, must end with "." + TTL uint32 `json:"ttl,omitempty"` + Metadata map[string]string `json:"meta,omitempty"` + NameFQDN string `json:"-"` // Must end with ".$origin". See below. + MxPreference uint16 `json:"mxpreference,omitempty"` // FIXME(tlim): Rename to MxPreference + SrvPriority uint16 `json:"srvpriority,omitempty"` + SrvWeight uint16 `json:"srvweight,omitempty"` + SrvPort uint16 `json:"srvport,omitempty"` Original interface{} `json:"-"` // Store pointer to provider-specific record object. Used in diffing. } -func (r *RecordConfig) String() string { - content := fmt.Sprintf("%s %s %s %d", r.Type, r.NameFQDN, r.Target, r.TTL) - if r.Type == "MX" { - content += fmt.Sprintf(" priority=%d", r.Priority) +func (r *RecordConfig) String() (content string) { + switch r.Type { + case "A", "AAAA", "PTR": + content = fmt.Sprintf("%s %s %s %d", r.Type, r.NameFQDN, r.Target, r.TTL) + case "MX": + content = fmt.Sprintf(" priority=%d", r.MxPreference) + case "SOA": + content = fmt.Sprintf("%s %s %s %d", r.Type, r.Name, r.Target, r.TTL) + default: + panic(fmt.Sprintf("rc.String rtype %v unimplemented", r.Type)) } for k, v := range r.Metadata { content += fmt.Sprintf(" %s=%s", k, v) @@ -82,52 +92,103 @@ func (r *RecordConfig) String() string { return content } +// Content combines Target and other fields into one string. +func (r *RecordConfig) Content() string { + + // If this is a pseudo record, just return the target. + if _, ok := dns.StringToType[r.Type]; !ok { + return r.Target + } + + // We cheat by converting to a dns.RR and use the String() function. + // Sadly that function always includes a header, which we must strip out. + // TODO(tlim): Request the dns project add a function that returns + // the string without the header. + rr := r.ToRR() + header := rr.Header().String() + full := rr.String() + if !strings.HasPrefix(full, header) { + panic("dns.Hdr.String() not acting as we expect") + } + return full[len(header):] +} + +// MergeToTarget combines "extra" fields into .Target, and zeros the merged fields. +func (r *RecordConfig) MergeToTarget() { + // Merge "extra" fields into the Target. + r.Target = r.Content() + + // Zap any fields that may have been merged. + r.MxPreference = 0 + r.SrvPriority = 0 + r.SrvWeight = 0 + r.SrvPort = 0 +} + /// Convert RecordConfig -> dns.RR. -func (r *RecordConfig) ToRR() dns.RR { +func (rc *RecordConfig) ToRR() dns.RR { - // Note: The label is a FQDN ending in a ".". It will not put "@" in the Name field. - - // NB(tlim): An alternative way to do this would be - // to create the rr via: rr := TypeToRR[x]() - // then set the parameters. A benchmark may find that - // faster. This was faster to implement. - - rdtype, ok := dns.StringToType[r.Type] + // Don't call this on fake types. + rdtype, ok := dns.StringToType[rc.Type] if !ok { - log.Fatalf("No such DNS type as (%#v)\n", r.Type) + log.Fatalf("No such DNS type as (%#v)\n", rc.Type) } - hdr := dns.RR_Header{ - Name: r.NameFQDN + ".", - Rrtype: rdtype, - Class: dns.ClassINET, - Ttl: r.TTL, + // Magicallly create an RR of the correct type. + rr := dns.TypeToRR[rdtype]() + + // Fill in the header. + rr.Header().Name = rc.NameFQDN + "." + rr.Header().Rrtype = rdtype + rr.Header().Class = dns.ClassINET + rr.Header().Ttl = rc.TTL + if rc.TTL == 0 { + rr.Header().Ttl = DefaultTTL } - // Handle some special cases: + // Fill in the data. switch rdtype { + case dns.TypeA: + rr.(*dns.A).A = net.ParseIP(rc.Target) + case dns.TypeCNAME: + rr.(*dns.CNAME).Target = rc.Target + case dns.TypePTR: + rr.(*dns.PTR).Ptr = rc.Target case dns.TypeMX: - // Has a Priority field. - return &dns.MX{Hdr: hdr, Preference: r.Priority, Mx: r.Target} + rr.(*dns.MX).Preference = rc.MxPreference + rr.(*dns.MX).Mx = rc.Target + case dns.TypeNS: + rr.(*dns.NS).Ns = rc.Target + case dns.TypeSOA: + t := strings.Replace(rc.Target, `\ `, ` `, -1) + parts := strings.Fields(t) + rr.(*dns.SOA).Ns = parts[0] + rr.(*dns.SOA).Mbox = parts[1] + rr.(*dns.SOA).Serial = atou32(parts[2]) + rr.(*dns.SOA).Refresh = atou32(parts[3]) + rr.(*dns.SOA).Retry = atou32(parts[4]) + rr.(*dns.SOA).Expire = atou32(parts[5]) + rr.(*dns.SOA).Minttl = atou32(parts[6]) + case dns.TypeSRV: + rr.(*dns.SRV).Priority = rc.SrvPriority + rr.(*dns.SRV).Weight = rc.SrvWeight + rr.(*dns.SRV).Port = rc.SrvPort + rr.(*dns.SRV).Target = rc.Target case dns.TypeTXT: - // Assure no problems due to quoting/unquoting: - return &dns.TXT{Hdr: hdr, Txt: []string{r.Target}} + rr.(*dns.TXT).Txt = []string{rc.Target} default: + panic(fmt.Sprintf("ToRR: Unimplemented rtype %v", rc.Type)) } - var ttl string - if r.TTL == 0 { - ttl = strconv.FormatUint(uint64(DefaultTTL), 10) - } else { - ttl = strconv.FormatUint(uint64(r.TTL), 10) - } + return rr +} - s := fmt.Sprintf("%s %s IN %s %s", r.NameFQDN, ttl, r.Type, r.Target) - rc, err := dns.NewRR(s) +func atou32(s string) uint32 { + i64, err := strconv.ParseInt(s, 10, 32) if err != nil { - log.Fatalf("NewRR rejected RecordConfig: %#v (t=%#v)\n%v\n", s, r.Target, err) + panic(fmt.Sprintf("atou32 failed (%v) (err=%v", s, err)) } - return rc + return uint32(i64) } type Nameserver struct { @@ -196,8 +257,8 @@ func (dc *DomainConfig) Punycode() error { func (dc *DomainConfig) CombineMXs() { for _, rec := range dc.Records { if rec.Type == "MX" { - rec.Target = fmt.Sprintf("%d %s", rec.Priority, rec.Target) - rec.Priority = 0 + rec.Target = fmt.Sprintf("%d %s", rec.MxPreference, rec.Target) + rec.MxPreference = 0 } } } diff --git a/models/dns_test.go b/models/dns_test.go index de502cd83..77e3c489c 100644 --- a/models/dns_test.go +++ b/models/dns_test.go @@ -24,12 +24,12 @@ func TestHasRecordTypeName(t *testing.T) { func TestRR(t *testing.T) { experiment := RecordConfig{ - Type: "A", - Name: "foo", - Target: "1.2.3.4", - TTL: 0, - NameFQDN: "foo.example.com", - Priority: 0, + Type: "A", + Name: "foo", + Target: "1.2.3.4", + TTL: 0, + NameFQDN: "foo.example.com", + MxPreference: 0, } expected := "foo.example.com.\t300\tIN\tA\t1.2.3.4" found := experiment.ToRR().String() diff --git a/pkg/js/helpers.js b/pkg/js/helpers.js index 21ac528e3..9445259e0 100644 --- a/pkg/js/helpers.js +++ b/pkg/js/helpers.js @@ -173,6 +173,15 @@ function PTR(name, target) { } } +// SRV(name,priority,weight,port,target, recordModifiers...) +function SRV(name, priority, weight, port, target) { + checkArgs([_.isString, _.isNumber, _.isNumber, _.isNumber, _.isString], arguments, "SRV expects (name, priority, weight, port, target)") + var mods = getModifiers(arguments,5) + return function(d) { + addRecordSRV(d, "SRV", name, priority, weight, port, target, mods) + } +} + // TXT(name,target, recordModifiers...) function TXT(name, target) { var mods = getModifiers(arguments,2) @@ -312,6 +321,33 @@ function addRecord(d,type,name,target,mods) { return rec; } +function addRecordSRV(d,type,name,srvpriority,srvweight,srvport,target,mods) { + var rec = {type: type, name: name, srvpriority: srvpriority, srvweight: srvweight, srvport: srvport, target: target, ttl:d.defaultTTL, meta:{}}; + // for each modifier, decide based on type: + // - Function: call is with the record as the argument + // - Object: merge it into the metadata + // FIXME(tlim): Factor this code out to its own function. + if (mods) { + for (var i = 0; i< mods.length; i++) { + var m = mods[i] + if (_.isFunction(m)) { + m(rec); + } else if (_.isObject(m)) { + //convert transforms to strings + if (m.transform && _.isArray(m.transform)){ + m.transform = format_tt(m.transform) + } + _.extend(rec.meta,m); + _.extend(rec.meta,m); + } else { + console.log("WARNING: Modifier type unsupported:", typeof m, "(Skipping!)"); + } + } + } + d.records.push(rec); + return rec; +} + //ip conversion functions from http://stackoverflow.com/a/8105740/121660 // via http://javascript.about.com/library/blipconvert.htm function IP(dot) diff --git a/pkg/js/static.go b/pkg/js/static.go index 4c7d394d8..a7aece00f 100644 --- a/pkg/js/static.go +++ b/pkg/js/static.go @@ -190,57 +190,60 @@ var _escData = map[string]*_escFile{ "/helpers.js": { local: "pkg/js/helpers.js", - size: 9590, + size: 11076, modtime: 0, compressed: ` -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 `, }, diff --git a/pkg/normalize/validate.go b/pkg/normalize/validate.go index a47f2890f..cb7d90fd0 100644 --- a/pkg/normalize/validate.go +++ b/pkg/normalize/validate.go @@ -55,6 +55,7 @@ func validateRecordTypes(rec *models.RecordConfig, domain string, pTypes []strin "CNAME": true, "IMPORT_TRANSFORM": false, "MX": true, + "SRV": true, "TXT": true, "NS": true, "PTR": true, @@ -96,7 +97,7 @@ func checkLabel(label string, rType string, domain string) error { } //underscores are warnings - if strings.ContainsRune(label, '_') { + if rType != "SRV" && strings.ContainsRune(label, '_') { //unless it is in our exclusion list ok := false for _, ex := range expectedUnderscores { @@ -146,6 +147,8 @@ func checkTargets(rec *models.RecordConfig, domain string) (errs []error) { check(checkTarget(target)) case "ALIAS": check(checkTarget(target)) + case "SRV": + check(checkTarget(target)) case "TXT", "IMPORT_TRANSFORM": default: if rec.Metadata["orig_custom_type"] != "" { @@ -203,7 +206,7 @@ func importTransform(srcDomain, dstDomain *models.DomainConfig, transforms []tra r := newRec() r.Target = transformCNAME(r.Target, srcDomain.Name, dstDomain.Name) dstDomain.Records = append(dstDomain.Records, r) - case "MX", "NS", "TXT": + case "MX", "NS", "SRV", "TXT": // Not imported. continue default: diff --git a/providers/bind/bindProvider.go b/providers/bind/bindProvider.go index 19feb3092..632978973 100644 --- a/providers/bind/bindProvider.go +++ b/providers/bind/bindProvider.go @@ -97,7 +97,7 @@ func rrToRecord(rr dns.RR, origin string, replaceSerial uint32) (models.RecordCo rc.Target = v.Target case *dns.MX: rc.Target = v.Mx - rc.Priority = v.Preference + rc.MxPreference = v.Preference case *dns.NS: rc.Target = v.Ns case *dns.PTR: @@ -114,10 +114,15 @@ func rrToRecord(rr dns.RR, origin string, replaceSerial uint32) (models.RecordCo } rc.Target = fmt.Sprintf("%v %v %v %v %v %v %v", v.Ns, v.Mbox, new_serial, v.Refresh, v.Retry, v.Expire, v.Minttl) + case *dns.SRV: + rc.Target = v.Target + rc.SrvPort = v.Port + rc.SrvWeight = v.Weight + rc.SrvPriority = v.Priority case *dns.TXT: rc.Target = strings.Join(v.Txt, " ") default: - log.Fatalf("Unimplemented zone record type=%s (%v)\n", rc.Type, rr) + log.Fatalf("rrToRecord: Unimplemented zone record type=%s (%v)\n", rc.Type, rr) } return rc, old_serial } @@ -129,12 +134,11 @@ func makeDefaultSOA(info SoaInfo, origin string) *models.RecordConfig { Name: "@", } soaRec.NameFQDN = dnsutil.AddOrigin(soaRec.Name, origin) - //TODO(cpeterson): are these sane defaults? if len(info.Ns) == 0 { - info.Ns = "DEFAULT_NOT_SET" + info.Ns = "DEFAULT_NOT_SET." } if len(info.Mbox) == 0 { - info.Mbox = "DEFAULT_NOT_SET" + info.Mbox = "DEFAULT_NOT_SET." } if info.Serial == 0 { info.Serial = 1 diff --git a/providers/bind/prettyzone.go b/providers/bind/prettyzone.go index cf969fd2a..77afccd39 100644 --- a/providers/bind/prettyzone.go +++ b/providers/bind/prettyzone.go @@ -39,18 +39,36 @@ func (z *zoneGenData) Less(i, j int) bool { if rrtypeA != rrtypeB { return zoneRrtypeLess(rrtypeA, rrtypeB) } - if rrtypeA == dns.TypeA { + switch rrtypeA { + case dns.TypeNS, dns.TypeTXT: + // pass through. + case dns.TypeA: ta2, tb2 := a.(*dns.A), b.(*dns.A) ipa, ipb := ta2.A.To4(), tb2.A.To4() if ipa == nil || ipb == nil { log.Fatalf("should not happen: IPs are not 4 bytes: %#v %#v", ta2, tb2) } return bytes.Compare(ipa, ipb) == -1 - } - if rrtypeA == dns.TypeMX { + case dns.TypeMX: ta2, tb2 := a.(*dns.MX), b.(*dns.MX) pa, pb := ta2.Preference, tb2.Preference return pa < pb + case dns.TypeSRV: + ta2, tb2 := a.(*dns.SRV), b.(*dns.SRV) + pa, pb := ta2.Port, tb2.Port + if pa != pb { + return pa < pb + } + pa, pb = ta2.Priority, tb2.Priority + if pa != pb { + return pa < pb + } + pa, pb = ta2.Weight, tb2.Weight + if pa != pb { + return pa < pb + } + default: + panic(fmt.Sprintf("zoneGenData Less: unimplemented rtype %v", dns.TypeToString[rrtypeA])) } return a.String() < b.String() } @@ -92,6 +110,7 @@ func WriteZoneFile(w io.Writer, records []dns.RR, origin string) error { // be easy to read and pleasant to the eye. // * Within a label, SOA and NS records are listed first. // * MX records are sorted numericly by preference value. + // * SRV records are sorted numericly by port, then priority, then weight. // * A records are sorted by IP address, not lexicographically. // * Repeated labels are removed. // * $TTL is used to eliminate clutter. The most common TTL value is used. @@ -148,7 +167,7 @@ func (z *zoneGenData) generateZoneFileHelper(w io.Writer) error { // items[2]: class if hdr.Class != dns.ClassINET { - log.Fatalf("Unimplemented class=%v", items[2]) + log.Fatalf("generateZoneFileHelper: Unimplemented class=%v", items[2]) } // items[3]: type diff --git a/providers/bind/prettyzone_test.go b/providers/bind/prettyzone_test.go index f4bc7150f..12ba47d3d 100644 --- a/providers/bind/prettyzone_test.go +++ b/providers/bind/prettyzone_test.go @@ -156,6 +156,31 @@ _domainkey IN TXT "vvvv" google._domainkey IN TXT "\"foo\"" ` +func TestWriteZoneFileSrv(t *testing.T) { + //exhibits explicit ttls and long name + r1, _ := dns.NewRR(`bosun.org. 300 IN SRV 10 10 9999 foo.com.`) + r2, _ := dns.NewRR(`bosun.org. 300 IN SRV 10 20 5050 foo.com.`) + r3, _ := dns.NewRR(`bosun.org. 300 IN SRV 10 10 5050 foo.com.`) + r4, _ := dns.NewRR(`bosun.org. 300 IN SRV 20 10 5050 foo.com.`) + r5, _ := dns.NewRR(`bosun.org. 300 IN SRV 10 10 5050 foo.com.`) + buf := &bytes.Buffer{} + WriteZoneFile(buf, []dns.RR{r1, r2, r3, r4, r5}, "bosun.org") + if buf.String() != testdataZFSRV { + t.Log(buf.String()) + t.Log(testdataZFSRV) + t.Fatalf("Zone file does not match.") + } + parseAndRegen(t, buf, testdataZFSRV) +} + +var testdataZFSRV = `$TTL 300 +@ IN SRV 10 10 5050 foo.com. + IN SRV 10 10 5050 foo.com. + IN SRV 10 20 5050 foo.com. + IN SRV 20 10 5050 foo.com. + IN SRV 10 10 9999 foo.com. +` + func TestWriteZoneFileOrder(t *testing.T) { var records []dns.RR for i, td := range []string{ diff --git a/providers/cloudflare/cloudflareProvider.go b/providers/cloudflare/cloudflareProvider.go index a847cf592..8a3e4541b 100644 --- a/providers/cloudflare/cloudflareProvider.go +++ b/providers/cloudflare/cloudflareProvider.go @@ -344,12 +344,12 @@ func (c *cfRecord) toRecord(domain string) *models.RecordConfig { c.Content = dnsutil.AddOrigin(c.Content+".", domain) } return &models.RecordConfig{ - NameFQDN: c.Name, - Type: c.Type, - Target: c.Content, - Priority: c.Priority, - TTL: c.TTL, - Original: c, + NameFQDN: c.Name, + Type: c.Type, + Target: c.Content, + MxPreference: c.Priority, + TTL: c.TTL, + Original: c, } } diff --git a/providers/cloudflare/rest.go b/providers/cloudflare/rest.go index eed8a92cf..4b4db1570 100644 --- a/providers/cloudflare/rest.go +++ b/providers/cloudflare/rest.go @@ -131,7 +131,7 @@ func (c *CloudflareApi) createRec(rec *models.RecordConfig, domainID string) []* } prio := "" if rec.Type == "MX" { - prio = fmt.Sprintf(" %d ", rec.Priority) + prio = fmt.Sprintf(" %d ", rec.MxPreference) } arr := []*models.Correction{{ Msg: fmt.Sprintf("CREATE record: %s %s %d%s %s", rec.Name, rec.Type, rec.TTL, prio, content), @@ -142,7 +142,7 @@ func (c *CloudflareApi) createRec(rec *models.RecordConfig, domainID string) []* Type: rec.Type, TTL: rec.TTL, Content: content, - Priority: rec.Priority, + Priority: rec.MxPreference, } endpoint := fmt.Sprintf(recordsURL, domainID) buf := &bytes.Buffer{} @@ -181,7 +181,7 @@ func (c *CloudflareApi) modifyRecord(domainID, recID string, proxied bool, rec * Priority uint16 `json:"priority"` TTL uint32 `json:"ttl"` } - r := record{recID, proxied, rec.Name, rec.Type, rec.Target, rec.Priority, rec.TTL} + r := record{recID, proxied, rec.Name, rec.Type, rec.Target, rec.MxPreference, rec.TTL} endpoint := fmt.Sprintf(singleRecordURL, domainID, recID) buf := &bytes.Buffer{} encoder := json.NewEncoder(buf) diff --git a/providers/diff/diff.go b/providers/diff/diff.go index eee58eab3..7d4822bdf 100644 --- a/providers/diff/diff.go +++ b/providers/diff/diff.go @@ -33,11 +33,7 @@ type differ struct { // get normalized content for record. target, ttl, mxprio, and specified metadata func (d *differ) content(r *models.RecordConfig) string { - content := fmt.Sprintf("%s %d", r.Target, r.TTL) - if r.Type == "MX" { - content += fmt.Sprintf(" priority=%d", r.Priority) - } - + content := fmt.Sprintf("%v ttl=%d", r.Content(), r.TTL) for _, f := range d.extraValues { for k, v := range f(r) { content += fmt.Sprintf(" %s=%s", k, v) diff --git a/providers/diff/diff_test.go b/providers/diff/diff_test.go index 644e6b050..458d0ccc8 100644 --- a/providers/diff/diff_test.go +++ b/providers/diff/diff_test.go @@ -94,8 +94,8 @@ func TestMxPrio(t *testing.T) { desired := []*models.RecordConfig{ myRecord("www MX 1 1.1.1.1"), } - existing[0].Priority = 10 - desired[0].Priority = 20 + existing[0].MxPreference = 10 + desired[0].MxPreference = 20 checkLengths(t, existing, desired, 0, 0, 0, 1) } diff --git a/providers/dnsimple/dnsimpleProvider.go b/providers/dnsimple/dnsimpleProvider.go index 356e29dc4..b25c14f2c 100644 --- a/providers/dnsimple/dnsimpleProvider.go +++ b/providers/dnsimple/dnsimpleProvider.go @@ -54,12 +54,12 @@ func (c *DnsimpleApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C r.Content += "." } rec := &models.RecordConfig{ - NameFQDN: dnsutil.AddOrigin(r.Name, dc.Name), - Type: r.Type, - Target: r.Content, - TTL: uint32(r.TTL), - Priority: uint16(r.Priority), - Original: r, + NameFQDN: dnsutil.AddOrigin(r.Name, dc.Name), + Type: r.Type, + Target: r.Content, + TTL: uint32(r.TTL), + MxPreference: uint16(r.Priority), + Original: r, } actual = append(actual, rec) } @@ -230,7 +230,7 @@ func (c *DnsimpleApi) createRecordFunc(rc *models.RecordConfig, domainName strin Type: rc.Type, Content: rc.Target, TTL: int(rc.TTL), - Priority: int(rc.Priority), + Priority: int(rc.MxPreference), } _, err = client.Zones.CreateRecord(accountId, domainName, record) @@ -277,7 +277,7 @@ func (c *DnsimpleApi) updateRecordFunc(old *dnsimpleapi.ZoneRecord, rc *models.R Type: rc.Type, Content: rc.Target, TTL: int(rc.TTL), - Priority: int(rc.Priority), + Priority: int(rc.MxPreference), } _, err = client.Zones.UpdateRecord(accountId, domainName, old.ID, record) diff --git a/providers/gandi/gandiProvider.go b/providers/gandi/gandiProvider.go index 6f9eb9eb1..7270e5cd0 100644 --- a/providers/gandi/gandiProvider.go +++ b/providers/gandi/gandiProvider.go @@ -146,5 +146,5 @@ func newGandi(m map[string]string, metadata json.RawMessage) (providers.DNSServi } func init() { - providers.RegisterDomainServiceProviderType("GANDI", newGandi) + providers.RegisterDomainServiceProviderType("GANDI", newGandi, providers.CanUsePTR) } diff --git a/providers/google/google.go b/providers/google/google.go index 26439ec0b..2f152d230 100644 --- a/providers/google/google.go +++ b/providers/google/google.go @@ -126,13 +126,7 @@ func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correc } for _, want := range dc.Records { - if want.Type == "MX" { - want.Target = fmt.Sprintf("%d %s", want.Priority, want.Target) - want.Priority = 0 - } else if want.Type == "TXT" { - //add quotes to txts - want.Target = fmt.Sprintf(`"%s"`, want.Target) - } + want.MergeToTarget() } // first collect keys that have changed diff --git a/providers/namedotcom/namedotcomProvider.go b/providers/namedotcom/namedotcomProvider.go index 37c876c1d..f0e12ef8d 100644 --- a/providers/namedotcom/namedotcomProvider.go +++ b/providers/namedotcom/namedotcomProvider.go @@ -41,7 +41,8 @@ func newProvider(conf map[string]string) (*nameDotCom, error) { func init() { providers.RegisterRegistrarType("NAMEDOTCOM", newReg) - providers.RegisterDomainServiceProviderType("NAMEDOTCOM", newDsp, providers.CanUseAlias) + providers.RegisterDomainServiceProviderType("NAMEDOTCOM", newDsp, providers.CanUseSRV) + // PTR records are not supported https://www.name.com/support/articles/205188508-Reverse-DNS-records (2017-05-08) } /// diff --git a/providers/namedotcom/records.go b/providers/namedotcom/records.go index 6564604f2..be32de64f 100644 --- a/providers/namedotcom/records.go +++ b/providers/namedotcom/records.go @@ -3,6 +3,7 @@ package namedotcom import ( "fmt" "strconv" + "strings" "github.com/miekg/dns/dnsutil" @@ -97,14 +98,31 @@ func checkNSModifications(dc *models.DomainConfig) { func (r *nameComRecord) toRecord() *models.RecordConfig { ttl, _ := strconv.ParseUint(r.TTL, 10, 32) prio, _ := strconv.ParseUint(r.Priority, 10, 16) - return &models.RecordConfig{ + rc := &models.RecordConfig{ NameFQDN: r.Name, Type: r.Type, Target: r.Content, TTL: uint32(ttl), - Priority: uint16(prio), Original: r, } + switch r.Type { + case "A", "AAAA", "CNAME", "NS", "TXT": + // nothing additional. + case "MX": + rc.MxPreference = uint16(prio) + case "SRV": + parts := strings.Split(r.Content, " ") + weight, _ := strconv.ParseInt(parts[0], 10, 32) + port, _ := strconv.ParseInt(parts[1], 10, 32) + rc.SrvWeight = uint16(weight) + rc.SrvPort = uint16(port) + rc.SrvPriority = uint16(prio) + rc.MxPreference = 0 + rc.Target = parts[2] + "." + default: + panic(fmt.Sprintf("toRecord unimplemented rtype %v", r.Type)) + } + return rc } type listRecordsResponse struct { @@ -150,11 +168,20 @@ func (n *nameDotCom) createRecord(rc *models.RecordConfig, domain string) error Type: rc.Type, Content: target, TTL: rc.TTL, - Priority: rc.Priority, + Priority: rc.MxPreference, } if dat.Hostname == "@" { dat.Hostname = "" } + switch rc.Type { + case "A", "AAAA", "CNAME", "MX", "NS", "TXT": + // nothing + case "SRV": + dat.Content = fmt.Sprintf("%d %d %v", rc.SrvWeight, rc.SrvPort, rc.Target) + dat.Priority = rc.SrvPriority + default: + panic(fmt.Sprintf("createRecord rtype %v unimplemented", rc.Type)) + } resp, err := n.post(n.apiCreateRecord(domain), dat) if err != nil { return err diff --git a/providers/providers.go b/providers/providers.go index dca1d4491..491bf3664 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -44,6 +44,8 @@ const ( CanUseAlias Capability = 1 << iota // CanUsePTR indicates the provider can handle PTR records CanUsePTR + // CanUseSRV indicates the provider can handle SRV records + CanUseSRV ) func ProviderHasCabability(pType string, cap Capability) bool { diff --git a/providers/route53/route53Provider.go b/providers/route53/route53Provider.go index c20c042ff..18e2c7bb9 100644 --- a/providers/route53/route53Provider.go +++ b/providers/route53/route53Provider.go @@ -42,7 +42,7 @@ func newRoute53(m map[string]string, metadata json.RawMessage) (providers.DNSSer } func init() { - providers.RegisterDomainServiceProviderType("ROUTE53", newRoute53, providers.CanUsePTR) + providers.RegisterDomainServiceProviderType("ROUTE53", newRoute53, providers.CanUsePTR, providers.CanUseSRV) } func sPtr(s string) *string { return &s @@ -142,12 +142,7 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode } } for _, want := range dc.Records { - if want.Type == "MX" { - want.Target = fmt.Sprintf("%d %s", want.Priority, want.Target) - want.Priority = 0 - } else if want.Type == "TXT" { - want.Target = fmt.Sprintf(`"%s"`, want.Target) //FIXME: better escaping/quoting - } + want.MergeToTarget() } //diff