diff --git a/docs/_includes/matrix.html b/docs/_includes/matrix.html
index 87c6833d1..fffa8f9f9 100644
--- a/docs/_includes/matrix.html
+++ b/docs/_includes/matrix.html
@@ -449,8 +449,8 @@
|
-
-
+ |
+
|
@@ -621,8 +621,8 @@
|
|
-
-
+ |
+
|
diff --git a/integrationTest/integration_test.go b/integrationTest/integration_test.go
index 654adb40c..5e3bf341e 100644
--- a/integrationTest/integration_test.go
+++ b/integrationTest/integration_test.go
@@ -459,6 +459,11 @@ func makeTests(t *testing.T) []*TestCase {
tc("Change Weight", srv("_sip._tcp", 52, 62, 7, "foo.com."), srv("_sip._tcp", 15, 65, 75, "foo4.com.")),
tc("Change Port", srv("_sip._tcp", 52, 62, 72, "foo.com."), srv("_sip._tcp", 15, 65, 75, "foo4.com.")),
)
+ if *providerToRun == "NAMEDOTCOM" {
+ t.Log("Skipping SRV Null Target test because provider does not support them")
+ } else {
+ tests = append(tests, tc("Null Target", srv("_sip._tcp", 52, 62, 72, "foo.com."), srv("_sip._tcp", 15, 65, 75, ".")))
+ }
}
// SSHFP
diff --git a/models/t_srv.go b/models/t_srv.go
index c6000d050..d08d2506b 100644
--- a/models/t_srv.go
+++ b/models/t_srv.go
@@ -48,10 +48,14 @@ func (rc *RecordConfig) SetTargetSRVStrings(priority, weight, port, target strin
// field as the SRV priority.
func (rc *RecordConfig) SetTargetSRVPriorityString(priority uint16, s string) error {
part := strings.Fields(s)
- if len(part) != 3 {
+ switch len(part) {
+ case 3:
+ return rc.setTargetSRVIntAndStrings(priority, part[0], part[1], part[2])
+ case 2:
+ return rc.setTargetSRVIntAndStrings(priority, part[0], part[1], ".")
+ default:
return errors.Errorf("SRV value does not contain 3 fields: (%#v)", s)
}
- return rc.setTargetSRVIntAndStrings(priority, part[0], part[1], part[2])
}
// SetTargetSRVString is like SetTargetSRV but accepts one big string to be parsed.
diff --git a/providers/cloudflare/cloudflareProvider.go b/providers/cloudflare/cloudflareProvider.go
index ba375997e..4c546fd91 100644
--- a/providers/cloudflare/cloudflareProvider.go
+++ b/providers/cloudflare/cloudflareProvider.go
@@ -422,23 +422,66 @@ func newCloudflare(m map[string]string, metadata json.RawMessage) (providers.DNS
// Used on the "existing" records.
type cfRecData struct {
- Name string `json:"name"`
- Target string `json:"target"`
- Service string `json:"service"` // SRV
- Proto string `json:"proto"` // SRV
- Priority uint16 `json:"priority"` // SRV
- Weight uint16 `json:"weight"` // SRV
- Port uint16 `json:"port"` // SRV
- Tag string `json:"tag"` // CAA
- Flags uint8 `json:"flags"` // CAA
- Value string `json:"value"` // CAA
- Usage uint8 `json:"usage"` // TLSA
- Selector uint8 `json:"selector"` // TLSA
- Matching_Type uint8 `json:"matching_type"` // TLSA
- Certificate string `json:"certificate"` // TLSA
- Algorithm uint8 `json:"algorithm"` // SSHFP
- Hash_Type uint8 `json:"type"` // SSHFP
- Fingerprint string `json:"fingerprint"` // SSHFP
+ Name string `json:"name"`
+ Target cfTarget `json:"target"`
+ Service string `json:"service"` // SRV
+ Proto string `json:"proto"` // SRV
+ Priority uint16 `json:"priority"` // SRV
+ Weight uint16 `json:"weight"` // SRV
+ Port uint16 `json:"port"` // SRV
+ Tag string `json:"tag"` // CAA
+ Flags uint8 `json:"flags"` // CAA
+ Value string `json:"value"` // CAA
+ Usage uint8 `json:"usage"` // TLSA
+ Selector uint8 `json:"selector"` // TLSA
+ Matching_Type uint8 `json:"matching_type"` // TLSA
+ Certificate string `json:"certificate"` // TLSA
+ Algorithm uint8 `json:"algorithm"` // SSHFP
+ Hash_Type uint8 `json:"type"` // SSHFP
+ Fingerprint string `json:"fingerprint"` // SSHFP
+}
+
+// cfTarget is a SRV target. A null target is represented by an empty string, but
+// a dot is so acceptable.
+type cfTarget string
+
+// UnmarshalJSON decodes a SRV target from the Cloudflare API. A null target is
+// represented by a false boolean or a dot. Domain names are FQDNs without a
+// trailing period (as of 2019-11-05).
+func (c *cfTarget) UnmarshalJSON(data []byte) error {
+ var obj interface{}
+ if err := json.Unmarshal(data, &obj); err != nil {
+ return err
+ }
+ switch v := obj.(type) {
+ case string:
+ *c = cfTarget(v)
+ case bool:
+ if v {
+ panic("unknown value for cfTarget bool: true")
+ }
+ *c = "" // the "." is already added by nativeToRecord
+ }
+ return nil
+}
+
+// MarshalJSON encodes cfTarget for the Cloudflare API. Null targets are
+// represented by a single period.
+func (c cfTarget) MarshalJSON() ([]byte, error) {
+ var obj string
+ switch c {
+ case "", ".":
+ obj = "."
+ default:
+ obj = string(c)
+ }
+ return json.Marshal(obj)
+}
+
+// DNSControlString returns cfTarget normalized to be a FQDN. Null targets are
+// represented by a single period.
+func (c cfTarget) FQDN() string {
+ return strings.TrimRight(string(c), ".") + "."
}
type cfRecord struct {
@@ -493,7 +536,7 @@ func (c *cfRecord) nativeToRecord(domain string) *models.RecordConfig {
case "SRV":
data := *c.Data
if err := rc.SetTargetSRV(data.Priority, data.Weight, data.Port,
- dnsutil.AddOrigin(data.Target+".", domain)); err != nil {
+ dnsutil.AddOrigin(data.Target.FQDN(), domain)); err != nil {
panic(errors.Wrap(err, "unparsable SRV record received from cloudflare"))
}
default: // "A", "AAAA", "ANAME", "CAA", "CNAME", "NS", "PTR", "TXT"
diff --git a/providers/cloudflare/rest.go b/providers/cloudflare/rest.go
index 5850c8c48..40322f83a 100644
--- a/providers/cloudflare/rest.go
+++ b/providers/cloudflare/rest.go
@@ -131,15 +131,16 @@ func (c *CloudflareApi) createZone(domainName string) (string, error) {
func cfSrvData(rec *models.RecordConfig) *cfRecData {
serverParts := strings.Split(rec.GetLabelFQDN(), ".")
- return &cfRecData{
+ c := &cfRecData{
Service: serverParts[0],
Proto: serverParts[1],
Name: strings.Join(serverParts[2:], "."),
Port: rec.SrvPort,
Priority: rec.SrvPriority,
Weight: rec.SrvWeight,
- Target: rec.GetTargetField(),
}
+ c.Target = cfTarget(rec.GetTargetField())
+ return c
}
func cfCaaData(rec *models.RecordConfig) *cfRecData {
diff --git a/providers/digitalocean/digitaloceanProvider.go b/providers/digitalocean/digitaloceanProvider.go
index 094ba3e0c..1e60790e5 100644
--- a/providers/digitalocean/digitaloceanProvider.go
+++ b/providers/digitalocean/digitaloceanProvider.go
@@ -199,6 +199,8 @@ func toRc(dc *models.DomainConfig, r *godo.DomainRecord) *models.RecordConfig {
// DO returns "@" on read even if fqdn was written.
if target == "@" {
target = dc.Name
+ } else if target == "." {
+ target = "" // don't append another dot to null records
}
target = dnsutil.AddOrigin(target+".", dc.Name)
// FIXME(tlim): The AddOrigin should be a no-op.
diff --git a/providers/dnsimple/dnsimpleProvider.go b/providers/dnsimple/dnsimpleProvider.go
index b1dd7a321..4a2adf1d7 100644
--- a/providers/dnsimple/dnsimpleProvider.go
+++ b/providers/dnsimple/dnsimpleProvider.go
@@ -71,7 +71,7 @@ func (c *DnsimpleApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C
if r.Name == "" {
r.Name = "@"
}
- if r.Type == "CNAME" || r.Type == "MX" || r.Type == "ALIAS" || r.Type == "SRV" {
+ if r.Type == "CNAME" || r.Type == "MX" || r.Type == "ALIAS" {
r.Content += "."
}
// dnsimple adds these odd txt records that mirror the alias records.
@@ -93,6 +93,10 @@ func (c *DnsimpleApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C
panic(errors.Wrap(err, "unparsable record received from dnsimple"))
}
case "SRV":
+ parts := strings.Fields(r.Content)
+ if len(parts) == 3 {
+ r.Content += "."
+ }
if err := rec.SetTargetSRVPriorityString(uint16(r.Priority), r.Content); err != nil {
panic(errors.Wrap(err, "unparsable record received from dnsimple"))
}
diff --git a/providers/namedotcom/namedotcomProvider.go b/providers/namedotcom/namedotcomProvider.go
index 97db7520a..ea0cd8915 100644
--- a/providers/namedotcom/namedotcomProvider.go
+++ b/providers/namedotcom/namedotcomProvider.go
@@ -22,7 +22,7 @@ type NameCom struct {
var features = providers.DocumentationNotes{
providers.CanUseAlias: providers.Can(),
providers.CanUsePTR: providers.Cannot("PTR records are not supported (See Link)", "https://www.name.com/support/articles/205188508-Reverse-DNS-records"),
- providers.CanUseSRV: providers.Can(),
+ providers.CanUseSRV: providers.Can("SRV records with empty targets are not supported"),
providers.CanUseTXTMulti: providers.Cannot(),
providers.DocCreateDomains: providers.Cannot("New domains require registration"),
providers.DocDualHost: providers.Cannot("Apex NS records not editable"),
diff --git a/providers/namedotcom/records.go b/providers/namedotcom/records.go
index 22cca72b0..e92fc143b 100644
--- a/providers/namedotcom/records.go
+++ b/providers/namedotcom/records.go
@@ -157,6 +157,9 @@ func (n *NameCom) createRecord(rc *models.RecordConfig, domain string) error {
case "TXT":
record.Answer = encodeTxt(rc.TxtStrings)
case "SRV":
+ if rc.GetTargetField() == "." {
+ return errors.New("SRV records with empty targets are not supported (as of 2019-11-05, the API returns 'Parameter Value Error - Invalid Srv Format')")
+ }
record.Answer = fmt.Sprintf("%d %d %v", rc.SrvWeight, rc.SrvPort, rc.GetTargetField())
record.Priority = uint32(rc.SrvPriority)
default:
|