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

@ -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. 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. 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.

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: `
H4sIAAAAAAAA/9w6aXPjuLHf/St6We+NyBGH8jHjfUUtX6LY8pYrvkqWN95SFBVMQhJmeBUAyuPMyr89 H4sIAAAAAAAA/+w6bXPbuNHf/Sv2OM8TkRFD2U7i69DHtqot33hq2R5ZSX2jqhqEhCQkfBsAlOLmlN/e
hYMkSEljuyqTD5kPXhHoG43uRvdaBcPAOCUht/p7eytEIczSOQTwbQ8AgOIFYZwiynyYTF25FqVsltNs wQtJkJIcu9N05m7qDzIJ7DsWuwssrYJhYJySkFunBwcrRCHM0jkE8OUAAIDiBWGcIsp8mExdORalbJbT
RSLcWM4SRFK5sLfWtCI8R0XMB3TBIIDJtL+3Ny/SkJMsBZISTlBM/oltRzFrcN7F/TsStKUQ3+u+Em5D bEUi3BjOEkRSOXCw0bQiPEdFzPt0wSCAyfT04GBepCEnWQokJZygmPwT245i1uC8j/sjErSlEO+bUyXc
kLUhyhV+HJWs7BQl2OVPOXYTzJGjxSFzsMWiU4knviAIwLocXN0NLizFaC3/Ct0pXghlBDkfJFGJ4su/ liAbQ5RrvB6VrOwUJdjlDzl2E8yRo8Uhc7DFoFOJJ94gCMAa9q/f9a8sxWgjf4XuFC+EMoKcD5KoRPHl
LgjivvyrRRTae7XGXl6wpU3xwunrk+AFTSWhDeFPU3ajzWHXnBQPQwGwpQrZXG5AEATQyR4+45B3HHj3 rwuCuC9/tYhCe6/W2MsLtrQpXjineiV4QVNJaEv485TdanPYNSfFw1AAbKlCNpcTEAQBdLIPH3HIOw68
DuwOyWdhlq4wZSRLWQdIqmg4xqGIBa8JCAHMM5ogPuPc3rLvtEwTsfztpmkcurJOxPKXrJPix1PpEsow eAF2h+SzMEtXmDKSpawDJFU0HGNRxIDXBIQA5hlNEJ9xbu+Yd1qmiVj+fNM0Fl1ZJ2L5t6yT4vW5dAll
lX2dysElYkOWCsivf2qpvq3FdpjRiPmTqSs88aZ2RLGrPW08vvBh35UUGabCEv5kum4Kl9MsxIydIrpg mMq+TuXgErEhSwXk149aqi8bMR1mNGL+ZOoKT7ytHVHMak8bj698OHQlRYapsIQ/mW6awuU0CzFj54gu
duJq5zWN3esJywJG4RKSLCJzgqkrzpJwIAyQ53kNWE3ZhxDFsQB6JHyp6ZqAiFL05JcCCJUKysgKx08m mJ242nlNY/d6wrKAUbiEJIvInGDqirUkHAgD5HleA1ZT9iFEcSyA1oQvNV0TEFGKHvxSAKFSQRlZ4fjB
lHIOcRR0gSXLlGfSEBHiqIIUd2PmEXamudtJw2FKv7G1ev1qZw04ZrjCHwihtiALC9jCbz5Lh9yk3bTj hFLOIZaCLrBkmfJMGiJCHFWQYm/MPMIuNHc7aThM6Te2Vu+0mtkAjhmu8PtCqB3IwgK28JuP0iG3aTft
5PO0MmUDcL2L8bXUcwvnmYe/cpxGWnRPqO4mmxqYWHxJs0ew/jYYXZ1f/eprSarTU3GjSFmR5xnlOPLB OPk4rUzZANzsY3wj9dzBeebhzxynkRbdE6q7ybYGJhZf0mwN1t/6o+vL6599LUm1eipuFCkr8jyjHEc+
6kJ5L6ELFiiHleuar/LrWo/13l6vB6dtn/bhhGLEMSA4vbrVdDy4Yxj4EkOOKEowx5QBYqUbA0ojIRzz WF0o9yV0wQLlsHJc81V+XeuxOTjo9eC87dM+nFGMOAYE59d3mo4H7xgGvsSQI4oSzDFlgFjpxoDSSAjH
ar/cIKwVlHdXqRPsvllK0OrQCASw3wfyixmEvRinC77sA+l2ncp6jXM0oCdk6hoHut5kcCgYILooEpzy vNovtwhrBeXeVeoE+3eWErRaNAIBHJ4C+ckMwl6M0wVfngLpdp3Keo11NKAnZOoaC7rZZnAsGCC6KBKc
JnXjcAR0AgFUgBMyrc264zbWsUuFIZVgdADSIPo8hmeDu4vxLegwxQABwxyyeal6zRl4BijP4yf5I45h 8iZ1Y3EEdAIBVIATMq3Numc31rFLhSGVYHQA0iB6PQYX/XdX4zvQYYoBAoY5ZPNS9Zoz8AxQnscP8iGO
XvCC4jJ/eYLeUNx6eZF5VhN/JHEMYYwRBZQ+QU7ximQFgxWKC8wEQ/MkNVaZYjfz4PazetGU5llKU5g2 YV7wguIyf3mC3kDsermReVYTX5M4hjDGiAJKHyCneEWygsEKxQVmgqG5khqrTLHbeXD3Wn3TlOZaSlOY
dcpcqOwyHl/YK8eHW8ylH47HF5Kl8lLlh4bMCtzIu+KK3nJK0oW9chzjOCGQtUu6GGenBUUy9qwcMxHr NnXKXKjsMh5f2SvHhzvMpR+Ox1eSpfJS5YeGzArcyLtii95xStKFvXIcYzkhkLVLuhhn5wVFMvasHDMR
8F7StqmpA/U4jyGAlSFuJcUWwvUlSBAPl1iYcOXJ33bvH/bfo65jT1iyjB7Tp+mfnP/paVGEDhVGAGkR 6/Be0rapqQP1OI8hgJUhbiXFDsL1JkgQD5dYmHDlyWe79w/771HXsScsWUbr9GH6J+f/eloUoUOFEUBa
x4YWKl6s5M0nDNKMAxKHSSKING8tjGUoVqSEQwAWs9osJodTg7qGq/fMVAyBiAkMn6e8wj6YOpWahcjS xLGhhYoXK7nzCYM044DEYpIIIs1bC2MZihUp4RCAxaw2i8nx1KCu4eo5MxVDIGICw5cpr7CPpk6lZiGy
FrP8AxesxPKP912wlpZ/dLy/r8WYWJE1hQAKbwnv4fBjufqoVyN4Dz+Xi6mxeLRfrj6Zq8eftGjvAygm tMUs/8gFK7H8k0MXrKXlvz45PNRiTKzImkIAhbeEl3D8phxd69EIXsKP5WBqDL4+LEcfzNGTt1q0lwEU
QvppI8OvyrtWpdmGa5X3rHQxuabCoHEpTNwf42dR4654dVHQcjeli1G+WWWJc4USbLmw74AASdlJVqQy EyH9tJHhV+Veq9Jsw7XKfVa6mBxTYdDYFCbu9/GzqLFXvLooaLmb0sUo36yyxLlGCbZcOHRAgKTsLCtS
lOxDglHKIMrSDgdRv2dU1ylYhQSj5vBMZOFaJXlNRKCjODaNs1FLanSnNFRZRJZkZR1ZpBGekxRHHcNw GUoOIcEoZRBlaYeDqN8zqusUrEKCUXN4JrJwrZK8JiLQURybxtmqJTW6UxqqLCJLsrKOLNIIz0mKo45h
FQR8OHiLtYyiaiJkEP6haTUjy0CJSPKyKrvUWZZ5nufUSmk4ILmZykTWgwAWmFdodRhzD52XZUVRNJJ8 uAoCXh09x1pGUTURMgj/0LSakaWvRCR5WZUNdZZlnuc5tVIaDkhupjKR9SCABeYVWh3G3GPn27KiKBpJ
7ci1BpZbSiMoO01JB4NXC1uB/mB5B4Pvi3xxPrjVzyFEF5i/JHcNDwrhRwovmGnptXQtDYQKJ1eDy+Eb vnbkWn3LLaURlJ2mpP3+k4WtQL+zvP3+4yJfXfbv9HEI0QXm35K7hgeF8D2FF8y09Fq6lgZChbPr/nDw
VDDgf7wKktl3Vej14GY8eoP8FfSPl/5mPHpJ9vH9+A2yV9A/Xvbx/fgl2S/vlTA5JRkl/Ol1OpRYUKG1 DBUM+O+vgmT2qAq9HtyOR8+Qv4L+/tLfjkffkv1u9F5Jk1OSUcIf3DUmiyV3ReH7NIUqElDRAE0EJJWW
lAmXOPwiiiZ7UmcbF8TvqyJ5EI+7en3q1vWiC9blPeCvOQ45g11cLOeVJjt6hcnko0DWdiUf4+Fj2lOI ouESh59EUWJP6mjugni+LpIP4vD02LOCn7p1neaCdTd6D/hzjkPO4GnCWM4Trf72OVYXtoiUPJYLTxHE
ZrlgHp4LLZNWJqotIH8xqSMT72YWOnUuRvUjAX5RSOV3u3ayJaqRXrY8PRoEWq8Oye8nBTEhU8laFLFO he1FGd+Pn+FQFfT3d6jx/fhbDjW8b/nTk3QosQxj/VtOs9M5hvf7feO53vD6CSaTJzVZcJd8jNOoaU8h
8y1Y8+pa8KE6GbC6pFtVbmFGKQ65fM9ZjvFiM33r6i1R9eo/FlKvvh9PheCDy+HtcPTbsBGTTGFbAC2h WuUme9yhMlFtAfnEpI7MhQiz0KkLJFSf3OAnhVS+twtaW6IaOX/HebBBoHUUlPx+UBATMpWsxcnCaR7Q
X8j7Zt0i/a7ZIZKkfP3f9TbfqptQnKKUic8ZRw+x7tqJkCT4TyZx9ujDgQtLslj6cOiKx+xfEMM+HE1d a15dC15VKwNWl3SrcjrMKMUhl4dsyzGO0aZvXT8n1V3/1/Lc9eNJTgjeHw7uBqP3g0aiMIVtAbSE/kYx
UNsfy+1Pcvv8xofj6VSRkX0Q6wCe4RCe4Qie+/ARnuETPAM8w7GoycUBxSTF6p21Z3plIHwSfoGWkNue ZhaT0u+a13aSlK//b3b5Vn0zyClKmXidcfQh1lepIiQJ/pNJnK19OHJhSRZLH45dSPH6L4hhH15PXVDT
WhI+h6ANWz1cBYCUDgIguSd/1q8O+dnwdKPRojZbXl7SmnkJyhWIW50Xcb6VjbYiOYwybhNn7XifM5La b8rpt3L68taHk+lUkZGXU9YRfIVj+Aqv4espvIGv8Ba+AnyFE3FQEgsUkxSrw++B6ZWB8En4CVpC7jr/
lmv6O44Z3k64xFTc+xtXxFBKnEillvhoKCYWvqOa3N5UTtOs1BPf/zYFNXFDRSnFbiXFyy+Aid6veOZe SvgcgjZsdZsgAKR0EADJPflYHwXla8PTjdsvNdny8pLWzEtQrkDcar2I86W8/SyS4yjjNnE2jvcxI6lt
nD067uaycMh6XUu/ZxhY/lZvPOl8uoucPWod4BksR6ghZNCqKkC93werbGecX95cj8az8WhwdXt2PbpU uaa/45jh3YRLTMX9dGuLGEqJFanUEi8NxcTAI6rJ6W3lNM1KPfH+H1NQEzdUlFLsV1IcxwOY6PmKZ+7F
lyqWzx/lhXWPpLqCr0dyOY9fFRhUMz2EoJV02qwsF6w/WxX5yqzq37dO6wp1/Ha8MKV01lOnkSCEtM0D 2dpxt4eFQ9bjWvoDw8DyWR28pfPpq/1srXWAr2A5Qg0hg1ZVAer5U7DKO6bL4e3NaDwbj/rXdxc3o6Ha
pzjUDQTO480z1vXb3ejXoW2WaHJBKxh5f8U4v0u/pNljCgHMUcxwGWyvZxvI1doOfE4L3IiI7dzAXMYR VLE8kyovrC+uqi34dCSX8/hJgUF1OEIIWkmnzcpywfqzVZGvzKr+vnRaW6jjt+OFKaWzmTqNBCGkbS44
3ZZFtvaCJHBftoN2doLqMqFMnJsvPQHTbH2bRym7/huZR7MQ0Xaug77MsrpMQowVCRbBEUURxYx5oCYO xaG+1eE83l5jXVS/G/08sM26WQ5oBSPvrxjn79JPabZOIYA5ihkug+3NbAu5GtuDz2mBGxGxnRuYyzii
HAj3Gm96VVnZOheZsmuy9ZXVMJuzHOF+38whxe7U5Ap/8M1Hf12pyZmAniTo4cb2Fn+EQxJheEAMR5Cl u7LIzgs6CXwq7+j2Xs/VZUKZOLeP3wKm2Y8wl1K2YrYyj2Yhou1cB32ZZXWZhBgrEiyCI4oiihnzQLWB
aj5Swn+As1ajn6lGP19iXU0AYvKrrAdq1OutTX0B22jsS1hlOR/Oz+DyvqasLC+Po1Ss7kIZZ7fhT6oY OBDuNS5aVGVl61xkyq7J1ltWw2w32IT7fTE7R/tTkyv8wTdvYupKTTZqdHtHd5x2910iHJIIwwfEcARZ
kx6zw5vAaNMKuAmZNvZeN2uAxKY4NAIvvKHpD0r90puqsCF7tqoPxDYRpO5eBQzv3oEx06g32jmpktjA qppWJfwruGh1X5jqvvAl1tUEICbfynqgRr3Z2WkRsI1ui4RVlvPh8gKG9zVlZXm5HKVi9dWgsXZb/qSK
bYzTDNRNxPXGUjWyEOFpY17xeqiWtfQdSuSgsB593ltbrCdoln4hjnEr4U0rhFnKMlEGZQu7Hp9c7pyb Mekxe7wJjLtzATch08bc0xpAkNgUh0bghWd0YkCpX3pTFTbkRbq6nGPbCFJ3rwKGFy/AaDTVE+2cVEls
WG41NnHBsm+/kDwn6eInx2qrsjX/Rp6egJST1rA5S6Q47KtQTHKoh5lVkmIwp1kCS85zv9djHIVfshWm 4DZ6nAbqNuJma6jqI4nwtNVEejpUy1p6DyWye1v3o++tHdYTNEu/EMu4k/C2FcIsZZkog7KFXfe0hnub
8zh79MIs6aHe/x3sf/r5437v4PDg+HhfxPQVQSXCZ7RCLKQk5x56yAoucWLyQBF96j3EJNf+5y15YqTX WZZb9bJcsOy7TyTPSbr4wbHaquzMv5Gn21Jl+ztsNngpDveELHU6rqMWo6vqJMboSh+Pxahx+WDuiyeE
GzvKuLNnzGMggCjjHstjwu2O12lqYct/3WiyP3XeH346drri42DqGF+Hja+jqdMaoZblTJGUjMlcfMnO FIOmb77IGcXBrx8VfEbVmHkufywo/Vbi0MXl/XBg85gkjg8XKOTytp0wCLMIQ1ZwsTEJZyCSYLlc3v8i
X9X4c8y5veRtNWbirQ6roLaJkhZJK/RGKjr/7+Gn4y0J6khU0v8v48qHD+p+GO1HISJcIr705nGWUcGz 0u8zIv1mAkevR3KoP02pPJPBnGYJLDnP/V6PcRR+ylaYzuNs7YVZ0kO9Pxwdvv3xzWHv6Pjo5ORQFIMr
J/Ss3cOgDl3oeB3oQrSlVRn1q5ZSnBXRPEYUA4oJYpj5qmGAuZz2cBEepJAkjciKRAWKy1mbJ/+niJOz gkqEj2iFWEhJzj30ISu4xInJB4roQ+9DTHLtJt6SJ0ZdfmtHGXcOjO46BBBl3GN5TLjd8TpNLWz5140m
2c3o+v732fXZmcgqnbAiOctp9vWp40Mnm887676UUVQRYhkiwkRpErXJXO2mkpZEDDI43Ubl7O7iYied h1Pn5fHbE6crXo6mjvF23Hh7PXVaH8SU56AiKRmTuXiTfZyqjeOYX2FJ3lbjC6dWv0xQ20ZJi6RVs0Wq
eRHHilJJpTtCJF4UaU1N7GD6oRx3mubw92oddIM+m89V2ks5qcZeYBs9fMdvCqhHWTutNtN4tfW2cE03 rPv/47cnOyrb1+II/ke5/V+9Um5sNJOEiDBEfOnN4yyjgmdP6Fm7h0EdutDxOtCFaEfjKTqtGgRxVkTz
me5is92qDS7Cusop7m7H15cu3Iyufzs/HY7g9mZ4cn52fgKj4cn16BTGv98Mb40+49lsNDw9Hw1Pxjaj GFEMKCaIYearm0bMZe+ei10shSRpRFYkKlBcfjnhyU/czi5mt6Ob+19mNxcXInd0workLKfZ54eOD51s
oQsRe90jWVwiRkOPpBH+ej2XjxL4KQjgwwH88Ycgs21rayfDojgislnBaCinwBHjkBRMDQqWaIUhzJIE Pu9sTqWM4vghhiEiTJxpojaZ6/1U0pKIQQanu6hcvLu62ktnXsSxolRS6Y4QiRdFWlMTM5i+Kj9eMc3h
sY1GBmy0Mmt9LFcU4YyGXcu1ukKvqh421R8PL2/+62zQUOo7hvhXAAAA///niFsRdiUAAA== H9Q66HZrNp+rPJVyUn3EALbRkXX8poD6w4S9VptpvNp6O7im20z3sdlt1QYXYV3lFO/uxjdDF25HN+8v
zwcjuLsdnF1eXJ7BaHB2MzqH8S+3gzuja3QxGw3OL0eDs7HNaOhCxJ52uyY2EaOhR9IIf76Zy9sM+CEI
4NUR/PqrILNraucVqEVxROQtJ6Oh/KYnYhySgqm27xKtMIRZkiC2dQMKW42pWh/LFad3RsOu5VpdoVd1
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