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

Add SRV Record Type (#136)

* Add support for SRV records for NAMEDOTCOM and ROUTE53.
* Improve docs
* Rename RR() to ToRR().
* Rename RecordConfig Priority to MxPreference (affects json IR data)
This commit is contained in:
Tom Limoncelli
2017-07-19 15:53:40 -04:00
committed by GitHub
parent 032750743c
commit d55b20ecdb
23 changed files with 351 additions and 161 deletions

View File

@ -119,7 +119,12 @@ func runTests(t *testing.T, prv providers.DNSServiceProvider, domainName string,
t.Fatal(err) t.Fatal(err)
} }
if !skipVal && i != *startIdx && len(corrections) == 0 { 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 { for _, c := range corrections {
if *verbose { if *verbose {
@ -215,7 +220,7 @@ func ns(name, target string) *rec {
func mx(name string, prio uint16, target string) *rec { func mx(name string, prio uint16, target string) *rec {
r := makeRec(name, target, "MX") r := makeRec(name, target, "MX")
r.Priority = prio r.MxPreference = prio
return r return r
} }
@ -223,6 +228,14 @@ func ptr(name, target string) *rec {
return makeRec(name, target, "PTR") 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 { func makeRec(name, target, typ string) *rec {
return &rec{ return &rec{
Name: name, Name: name,
@ -257,8 +270,8 @@ var tests = []*TestCase{
tc("Change it", a("@", "1.2.3.4")), 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", 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("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 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(100), a("www", "2.2.2.2"), 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("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("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)), 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("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("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 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 //PTR
tc("Empty"), tc("Empty"),
@ -298,11 +311,22 @@ var tests = []*TestCase{
tc("Modify PTR record", ptr("4", "bar.com.")), tc("Modify PTR record", ptr("4", "bar.com.")),
//ALIAS //ALIAS
tc("EMPTY"), tc("Empty"),
tc("ALIAS at root", alias("@", "foo.com.")).IfHasCapability(providers.CanUseAlias), tc("ALIAS at root", alias("@", "foo.com.")).IfHasCapability(providers.CanUseAlias),
tc("change it", alias("@", "foo2.com.")).IfHasCapability(providers.CanUseAlias), tc("change it", alias("@", "foo2.com.")).IfHasCapability(providers.CanUseAlias),
tc("ALIAS at subdomain", alias("test", "foo.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. //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.")), //tc("IDN pre-punycoded", cname("xn--o-0gab", "xn--o-0gab.xn--o-0gab.")),
} }

View File

@ -20,7 +20,7 @@
"token": "$DNSIMPLE_TOKEN" "token": "$DNSIMPLE_TOKEN"
}, },
"GANDI": { "GANDI": {
"COMMENT": "5: gandi does not accept ttls less than 300", "COMMENT": "5: gandi does not accept TTLs less than 300",
"apikey": "$GANDI_KEY", "apikey": "$GANDI_KEY",
"domain": "$GANDI_DOMAIN", "domain": "$GANDI_DOMAIN",
"knownFailures": "5" "knownFailures": "5"

View File

@ -1,4 +1,4 @@
$TTL 300 $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 ns1.otherdomain.tld.
IN NS ns2.otherdomain.tld. IN NS ns2.otherdomain.tld.

View File

@ -9,6 +9,7 @@ import (
"net" "net"
"reflect" "reflect"
"strconv" "strconv"
"strings"
"github.com/StackExchange/dnscontrol/pkg/transform" "github.com/StackExchange/dnscontrol/pkg/transform"
"github.com/miekg/dns" "github.com/miekg/dns"
@ -60,21 +61,30 @@ type DNSProviderConfig struct {
// This is the FQDN version of Name. // This is the FQDN version of Name.
// It should never have a trailiing ".". // It should never have a trailiing ".".
type RecordConfig struct { type RecordConfig struct {
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` // The short name. See below. Name string `json:"name"` // The short name. See below.
Target string `json:"target"` // If a name, must end with "." Target string `json:"target"` // If a name, must end with "."
TTL uint32 `json:"ttl,omitempty"` TTL uint32 `json:"ttl,omitempty"`
Metadata map[string]string `json:"meta,omitempty"` Metadata map[string]string `json:"meta,omitempty"`
NameFQDN string `json:"-"` // Must end with ".$origin". See below. NameFQDN string `json:"-"` // Must end with ".$origin". See below.
Priority uint16 `json:"priority,omitempty"` 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. Original interface{} `json:"-"` // Store pointer to provider-specific record object. Used in diffing.
} }
func (r *RecordConfig) String() string { func (r *RecordConfig) String() (content string) {
content := fmt.Sprintf("%s %s %s %d", r.Type, r.NameFQDN, r.Target, r.TTL) switch r.Type {
if r.Type == "MX" { case "A", "AAAA", "PTR":
content += fmt.Sprintf(" priority=%d", r.Priority) 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 { for k, v := range r.Metadata {
content += fmt.Sprintf(" %s=%s", k, v) content += fmt.Sprintf(" %s=%s", k, v)
@ -82,52 +92,103 @@ func (r *RecordConfig) String() string {
return content 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. /// 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. // Don't call this on fake types.
rdtype, ok := dns.StringToType[rc.Type]
// 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]
if !ok { 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{ // Magicallly create an RR of the correct type.
Name: r.NameFQDN + ".", rr := dns.TypeToRR[rdtype]()
Rrtype: rdtype,
Class: dns.ClassINET, // Fill in the header.
Ttl: r.TTL, 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 { 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: case dns.TypeMX:
// Has a Priority field. rr.(*dns.MX).Preference = rc.MxPreference
return &dns.MX{Hdr: hdr, Preference: r.Priority, Mx: r.Target} 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: case dns.TypeTXT:
// Assure no problems due to quoting/unquoting: rr.(*dns.TXT).Txt = []string{rc.Target}
return &dns.TXT{Hdr: hdr, Txt: []string{r.Target}}
default: default:
panic(fmt.Sprintf("ToRR: Unimplemented rtype %v", rc.Type))
} }
var ttl string return rr
if r.TTL == 0 { }
ttl = strconv.FormatUint(uint64(DefaultTTL), 10)
} else {
ttl = strconv.FormatUint(uint64(r.TTL), 10)
}
s := fmt.Sprintf("%s %s IN %s %s", r.NameFQDN, ttl, r.Type, r.Target) func atou32(s string) uint32 {
rc, err := dns.NewRR(s) i64, err := strconv.ParseInt(s, 10, 32)
if err != nil { 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 { type Nameserver struct {
@ -196,8 +257,8 @@ func (dc *DomainConfig) Punycode() error {
func (dc *DomainConfig) CombineMXs() { func (dc *DomainConfig) CombineMXs() {
for _, rec := range dc.Records { for _, rec := range dc.Records {
if rec.Type == "MX" { if rec.Type == "MX" {
rec.Target = fmt.Sprintf("%d %s", rec.Priority, rec.Target) rec.Target = fmt.Sprintf("%d %s", rec.MxPreference, rec.Target)
rec.Priority = 0 rec.MxPreference = 0
} }
} }
} }

View File

@ -24,12 +24,12 @@ func TestHasRecordTypeName(t *testing.T) {
func TestRR(t *testing.T) { func TestRR(t *testing.T) {
experiment := RecordConfig{ experiment := RecordConfig{
Type: "A", Type: "A",
Name: "foo", Name: "foo",
Target: "1.2.3.4", Target: "1.2.3.4",
TTL: 0, TTL: 0,
NameFQDN: "foo.example.com", NameFQDN: "foo.example.com",
Priority: 0, MxPreference: 0,
} }
expected := "foo.example.com.\t300\tIN\tA\t1.2.3.4" expected := "foo.example.com.\t300\tIN\tA\t1.2.3.4"
found := experiment.ToRR().String() found := experiment.ToRR().String()

View File

@ -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...) // TXT(name,target, recordModifiers...)
function TXT(name, target) { function TXT(name, target) {
var mods = getModifiers(arguments,2) var mods = getModifiers(arguments,2)
@ -312,6 +321,33 @@ function addRecord(d,type,name,target,mods) {
return rec; 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 //ip conversion functions from http://stackoverflow.com/a/8105740/121660
// via http://javascript.about.com/library/blipconvert.htm // via http://javascript.about.com/library/blipconvert.htm
function IP(dot) function IP(dot)

View File

@ -190,57 +190,60 @@ var _escData = map[string]*_escFile{
"/helpers.js": { "/helpers.js": {
local: "pkg/js/helpers.js", local: "pkg/js/helpers.js",
size: 9590, size: 11076,
modtime: 0, modtime: 0,
compressed: ` compressed: `
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
kDbVHw+Gt787GzSUesQQ/woAAP//mqS3P0QrAAA=
`, `,
}, },

View File

@ -55,6 +55,7 @@ func validateRecordTypes(rec *models.RecordConfig, domain string, pTypes []strin
"CNAME": true, "CNAME": true,
"IMPORT_TRANSFORM": false, "IMPORT_TRANSFORM": false,
"MX": true, "MX": true,
"SRV": true,
"TXT": true, "TXT": true,
"NS": true, "NS": true,
"PTR": true, "PTR": true,
@ -96,7 +97,7 @@ func checkLabel(label string, rType string, domain string) error {
} }
//underscores are warnings //underscores are warnings
if strings.ContainsRune(label, '_') { if rType != "SRV" && strings.ContainsRune(label, '_') {
//unless it is in our exclusion list //unless it is in our exclusion list
ok := false ok := false
for _, ex := range expectedUnderscores { for _, ex := range expectedUnderscores {
@ -146,6 +147,8 @@ func checkTargets(rec *models.RecordConfig, domain string) (errs []error) {
check(checkTarget(target)) check(checkTarget(target))
case "ALIAS": case "ALIAS":
check(checkTarget(target)) check(checkTarget(target))
case "SRV":
check(checkTarget(target))
case "TXT", "IMPORT_TRANSFORM": case "TXT", "IMPORT_TRANSFORM":
default: default:
if rec.Metadata["orig_custom_type"] != "" { if rec.Metadata["orig_custom_type"] != "" {
@ -203,7 +206,7 @@ func importTransform(srcDomain, dstDomain *models.DomainConfig, transforms []tra
r := newRec() r := newRec()
r.Target = transformCNAME(r.Target, srcDomain.Name, dstDomain.Name) r.Target = transformCNAME(r.Target, srcDomain.Name, dstDomain.Name)
dstDomain.Records = append(dstDomain.Records, r) dstDomain.Records = append(dstDomain.Records, r)
case "MX", "NS", "TXT": case "MX", "NS", "SRV", "TXT":
// Not imported. // Not imported.
continue continue
default: default:

View File

@ -97,7 +97,7 @@ func rrToRecord(rr dns.RR, origin string, replaceSerial uint32) (models.RecordCo
rc.Target = v.Target rc.Target = v.Target
case *dns.MX: case *dns.MX:
rc.Target = v.Mx rc.Target = v.Mx
rc.Priority = v.Preference rc.MxPreference = v.Preference
case *dns.NS: case *dns.NS:
rc.Target = v.Ns rc.Target = v.Ns
case *dns.PTR: 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", 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) 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: case *dns.TXT:
rc.Target = strings.Join(v.Txt, " ") rc.Target = strings.Join(v.Txt, " ")
default: 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 return rc, old_serial
} }
@ -129,12 +134,11 @@ func makeDefaultSOA(info SoaInfo, origin string) *models.RecordConfig {
Name: "@", Name: "@",
} }
soaRec.NameFQDN = dnsutil.AddOrigin(soaRec.Name, origin) soaRec.NameFQDN = dnsutil.AddOrigin(soaRec.Name, origin)
//TODO(cpeterson): are these sane defaults?
if len(info.Ns) == 0 { if len(info.Ns) == 0 {
info.Ns = "DEFAULT_NOT_SET" info.Ns = "DEFAULT_NOT_SET."
} }
if len(info.Mbox) == 0 { if len(info.Mbox) == 0 {
info.Mbox = "DEFAULT_NOT_SET" info.Mbox = "DEFAULT_NOT_SET."
} }
if info.Serial == 0 { if info.Serial == 0 {
info.Serial = 1 info.Serial = 1

View File

@ -39,18 +39,36 @@ func (z *zoneGenData) Less(i, j int) bool {
if rrtypeA != rrtypeB { if rrtypeA != rrtypeB {
return zoneRrtypeLess(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) ta2, tb2 := a.(*dns.A), b.(*dns.A)
ipa, ipb := ta2.A.To4(), tb2.A.To4() ipa, ipb := ta2.A.To4(), tb2.A.To4()
if ipa == nil || ipb == nil { if ipa == nil || ipb == nil {
log.Fatalf("should not happen: IPs are not 4 bytes: %#v %#v", ta2, tb2) log.Fatalf("should not happen: IPs are not 4 bytes: %#v %#v", ta2, tb2)
} }
return bytes.Compare(ipa, ipb) == -1 return bytes.Compare(ipa, ipb) == -1
} case dns.TypeMX:
if rrtypeA == dns.TypeMX {
ta2, tb2 := a.(*dns.MX), b.(*dns.MX) ta2, tb2 := a.(*dns.MX), b.(*dns.MX)
pa, pb := ta2.Preference, tb2.Preference pa, pb := ta2.Preference, tb2.Preference
return pa < pb 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() 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. // be easy to read and pleasant to the eye.
// * Within a label, SOA and NS records are listed first. // * Within a label, SOA and NS records are listed first.
// * MX records are sorted numericly by preference value. // * 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. // * A records are sorted by IP address, not lexicographically.
// * Repeated labels are removed. // * Repeated labels are removed.
// * $TTL is used to eliminate clutter. The most common TTL value is used. // * $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 // items[2]: class
if hdr.Class != dns.ClassINET { if hdr.Class != dns.ClassINET {
log.Fatalf("Unimplemented class=%v", items[2]) log.Fatalf("generateZoneFileHelper: Unimplemented class=%v", items[2])
} }
// items[3]: type // items[3]: type

View File

@ -156,6 +156,31 @@ _domainkey IN TXT "vvvv"
google._domainkey IN TXT "\"foo\"" 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) { func TestWriteZoneFileOrder(t *testing.T) {
var records []dns.RR var records []dns.RR
for i, td := range []string{ for i, td := range []string{

View File

@ -344,12 +344,12 @@ func (c *cfRecord) toRecord(domain string) *models.RecordConfig {
c.Content = dnsutil.AddOrigin(c.Content+".", domain) c.Content = dnsutil.AddOrigin(c.Content+".", domain)
} }
return &models.RecordConfig{ return &models.RecordConfig{
NameFQDN: c.Name, NameFQDN: c.Name,
Type: c.Type, Type: c.Type,
Target: c.Content, Target: c.Content,
Priority: c.Priority, MxPreference: c.Priority,
TTL: c.TTL, TTL: c.TTL,
Original: c, Original: c,
} }
} }

View File

@ -131,7 +131,7 @@ func (c *CloudflareApi) createRec(rec *models.RecordConfig, domainID string) []*
} }
prio := "" prio := ""
if rec.Type == "MX" { if rec.Type == "MX" {
prio = fmt.Sprintf(" %d ", rec.Priority) prio = fmt.Sprintf(" %d ", rec.MxPreference)
} }
arr := []*models.Correction{{ arr := []*models.Correction{{
Msg: fmt.Sprintf("CREATE record: %s %s %d%s %s", rec.Name, rec.Type, rec.TTL, prio, content), 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, Type: rec.Type,
TTL: rec.TTL, TTL: rec.TTL,
Content: content, Content: content,
Priority: rec.Priority, Priority: rec.MxPreference,
} }
endpoint := fmt.Sprintf(recordsURL, domainID) endpoint := fmt.Sprintf(recordsURL, domainID)
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
@ -181,7 +181,7 @@ func (c *CloudflareApi) modifyRecord(domainID, recID string, proxied bool, rec *
Priority uint16 `json:"priority"` Priority uint16 `json:"priority"`
TTL uint32 `json:"ttl"` 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) endpoint := fmt.Sprintf(singleRecordURL, domainID, recID)
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
encoder := json.NewEncoder(buf) encoder := json.NewEncoder(buf)

View File

@ -33,11 +33,7 @@ type differ struct {
// get normalized content for record. target, ttl, mxprio, and specified metadata // get normalized content for record. target, ttl, mxprio, and specified metadata
func (d *differ) content(r *models.RecordConfig) string { func (d *differ) content(r *models.RecordConfig) string {
content := fmt.Sprintf("%s %d", r.Target, r.TTL) content := fmt.Sprintf("%v ttl=%d", r.Content(), r.TTL)
if r.Type == "MX" {
content += fmt.Sprintf(" priority=%d", r.Priority)
}
for _, f := range d.extraValues { for _, f := range d.extraValues {
for k, v := range f(r) { for k, v := range f(r) {
content += fmt.Sprintf(" %s=%s", k, v) content += fmt.Sprintf(" %s=%s", k, v)

View File

@ -94,8 +94,8 @@ func TestMxPrio(t *testing.T) {
desired := []*models.RecordConfig{ desired := []*models.RecordConfig{
myRecord("www MX 1 1.1.1.1"), myRecord("www MX 1 1.1.1.1"),
} }
existing[0].Priority = 10 existing[0].MxPreference = 10
desired[0].Priority = 20 desired[0].MxPreference = 20
checkLengths(t, existing, desired, 0, 0, 0, 1) checkLengths(t, existing, desired, 0, 0, 0, 1)
} }

View File

@ -54,12 +54,12 @@ func (c *DnsimpleApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C
r.Content += "." r.Content += "."
} }
rec := &models.RecordConfig{ rec := &models.RecordConfig{
NameFQDN: dnsutil.AddOrigin(r.Name, dc.Name), NameFQDN: dnsutil.AddOrigin(r.Name, dc.Name),
Type: r.Type, Type: r.Type,
Target: r.Content, Target: r.Content,
TTL: uint32(r.TTL), TTL: uint32(r.TTL),
Priority: uint16(r.Priority), MxPreference: uint16(r.Priority),
Original: r, Original: r,
} }
actual = append(actual, rec) actual = append(actual, rec)
} }
@ -230,7 +230,7 @@ func (c *DnsimpleApi) createRecordFunc(rc *models.RecordConfig, domainName strin
Type: rc.Type, Type: rc.Type,
Content: rc.Target, Content: rc.Target,
TTL: int(rc.TTL), TTL: int(rc.TTL),
Priority: int(rc.Priority), Priority: int(rc.MxPreference),
} }
_, err = client.Zones.CreateRecord(accountId, domainName, record) _, err = client.Zones.CreateRecord(accountId, domainName, record)
@ -277,7 +277,7 @@ func (c *DnsimpleApi) updateRecordFunc(old *dnsimpleapi.ZoneRecord, rc *models.R
Type: rc.Type, Type: rc.Type,
Content: rc.Target, Content: rc.Target,
TTL: int(rc.TTL), TTL: int(rc.TTL),
Priority: int(rc.Priority), Priority: int(rc.MxPreference),
} }
_, err = client.Zones.UpdateRecord(accountId, domainName, old.ID, record) _, err = client.Zones.UpdateRecord(accountId, domainName, old.ID, record)

View File

@ -146,5 +146,5 @@ func newGandi(m map[string]string, metadata json.RawMessage) (providers.DNSServi
} }
func init() { func init() {
providers.RegisterDomainServiceProviderType("GANDI", newGandi) providers.RegisterDomainServiceProviderType("GANDI", newGandi, providers.CanUsePTR)
} }

View File

@ -126,13 +126,7 @@ func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correc
} }
for _, want := range dc.Records { for _, want := range dc.Records {
if want.Type == "MX" { want.MergeToTarget()
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)
}
} }
// first collect keys that have changed // first collect keys that have changed

View File

@ -41,7 +41,8 @@ func newProvider(conf map[string]string) (*nameDotCom, error) {
func init() { func init() {
providers.RegisterRegistrarType("NAMEDOTCOM", newReg) 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)
} }
/// ///

View File

@ -3,6 +3,7 @@ package namedotcom
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"strings"
"github.com/miekg/dns/dnsutil" "github.com/miekg/dns/dnsutil"
@ -97,14 +98,31 @@ func checkNSModifications(dc *models.DomainConfig) {
func (r *nameComRecord) toRecord() *models.RecordConfig { func (r *nameComRecord) toRecord() *models.RecordConfig {
ttl, _ := strconv.ParseUint(r.TTL, 10, 32) ttl, _ := strconv.ParseUint(r.TTL, 10, 32)
prio, _ := strconv.ParseUint(r.Priority, 10, 16) prio, _ := strconv.ParseUint(r.Priority, 10, 16)
return &models.RecordConfig{ rc := &models.RecordConfig{
NameFQDN: r.Name, NameFQDN: r.Name,
Type: r.Type, Type: r.Type,
Target: r.Content, Target: r.Content,
TTL: uint32(ttl), TTL: uint32(ttl),
Priority: uint16(prio),
Original: r, 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 { type listRecordsResponse struct {
@ -150,11 +168,20 @@ func (n *nameDotCom) createRecord(rc *models.RecordConfig, domain string) error
Type: rc.Type, Type: rc.Type,
Content: target, Content: target,
TTL: rc.TTL, TTL: rc.TTL,
Priority: rc.Priority, Priority: rc.MxPreference,
} }
if dat.Hostname == "@" { if dat.Hostname == "@" {
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) resp, err := n.post(n.apiCreateRecord(domain), dat)
if err != nil { if err != nil {
return err return err

View File

@ -44,6 +44,8 @@ const (
CanUseAlias Capability = 1 << iota CanUseAlias Capability = 1 << iota
// CanUsePTR indicates the provider can handle PTR records // CanUsePTR indicates the provider can handle PTR records
CanUsePTR CanUsePTR
// CanUseSRV indicates the provider can handle SRV records
CanUseSRV
) )
func ProviderHasCabability(pType string, cap Capability) bool { func ProviderHasCabability(pType string, cap Capability) bool {

View File

@ -42,7 +42,7 @@ func newRoute53(m map[string]string, metadata json.RawMessage) (providers.DNSSer
} }
func init() { func init() {
providers.RegisterDomainServiceProviderType("ROUTE53", newRoute53, providers.CanUsePTR) providers.RegisterDomainServiceProviderType("ROUTE53", newRoute53, providers.CanUsePTR, providers.CanUseSRV)
} }
func sPtr(s string) *string { func sPtr(s string) *string {
return &s return &s
@ -142,12 +142,7 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
} }
} }
for _, want := range dc.Records { for _, want := range dc.Records {
if want.Type == "MX" { want.MergeToTarget()
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
}
} }
//diff //diff