mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
40
docs/_functions/domain/PTR.md
Normal file
40
docs/_functions/domain/PTR.md
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
name: PTR
|
||||||
|
parameters:
|
||||||
|
- name
|
||||||
|
- target
|
||||||
|
- modifiers...
|
||||||
|
---
|
||||||
|
|
||||||
|
PTR adds a PTR record to the domain.
|
||||||
|
|
||||||
|
The name should be the relative label for the domain, or may be a FQDN that ends with `.`.
|
||||||
|
|
||||||
|
* If the name is a valid IP address, DNSControl will *magically* replace it with a string that is appropriate for the domain. That is, if the domain ends with `in-addr.arpa` it will generate the IPv4-style reverse name; if the domain ends with `ipv6.arpa` it will generate the IPv6-style reverse name. DNSControl will truncate it as appropriate for the netmask.
|
||||||
|
* If the name ends with `in-addr.arpa.` or `ipv6.arpa.` (not the `.` at the end), DNSControl will truncate it as appropriate for the domain. If the FQDN does not fit within the domain, this will raise an error.
|
||||||
|
|
||||||
|
Target should be a string representing the FQDN of a host. Like all FQDNs in DNSControl, it must end with a `.`.
|
||||||
|
|
||||||
|
{% include startExample.html %}
|
||||||
|
{% highlight js %}
|
||||||
|
D(REV('1.2.3.0/24'), REGISTRAR, DnsProvider(BIND),
|
||||||
|
PTR('1', 'foo.example.com.'),
|
||||||
|
PTR('2', 'bar.example.com.'),
|
||||||
|
PTR('3', 'baz.example.com.'),
|
||||||
|
// If the first parameter is a valid IP address, DNSControl will generate the correct name:
|
||||||
|
PTR('1.2.3.10', 'ten.example.com.'), // '10'
|
||||||
|
);
|
||||||
|
|
||||||
|
D(REV('2001:db8:302::/48'), REGISTRAR, DnsProvider(BIND),
|
||||||
|
PTR('1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0', 'foo.example.com.'), // 2001:db8:302::1
|
||||||
|
// If the first parameter is a valid IP address, DNSControl will generate the correct name:
|
||||||
|
PTR('2001:db8:302::2', 'two.example.com.'), // '2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0'
|
||||||
|
PTR('2001:db8:302::3', 'three.example.com.'), // '3.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0'
|
||||||
|
);
|
||||||
|
|
||||||
|
{%endhighlight%}
|
||||||
|
{% include endExample.html %}
|
||||||
|
|
||||||
|
In the future we plan on adding a flag to `A()` which will insert
|
||||||
|
the correct PTR() record if the approprate `.arpa` domain has been
|
||||||
|
defined.
|
@ -22,11 +22,21 @@ D(REV('1.2.3.0/24'), REGISTRAR, DnsProvider(BIND),
|
|||||||
PTR("1", 'foo.example.com.'),
|
PTR("1", 'foo.example.com.'),
|
||||||
PTR("2", 'bar.example.com.'),
|
PTR("2", 'bar.example.com.'),
|
||||||
PTR("3", 'baz.example.com.'),
|
PTR("3", 'baz.example.com.'),
|
||||||
|
// These take advantage of DNSControl's ability to generate the right name:
|
||||||
|
PTR("1.2.3.10", 'ten.example.com.'),
|
||||||
);
|
);
|
||||||
|
|
||||||
D(REV('2001:db8:302::/48'), REGISTRAR, DnsProvider(BIND),
|
D(REV('2001:db8:302::/48'), REGISTRAR, DnsProvider(BIND),
|
||||||
PTR("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0", 'foo.example.com.'), // 2001:db8:302::1
|
PTR("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0", 'foo.example.com.'), // 2001:db8:302::1
|
||||||
|
// These take advantage of DNSControl's ability to generate the right name:
|
||||||
|
PTR("2001:db8:302::2", 'two.example.com.'), // 2.0.0. etc. etc.
|
||||||
|
PTR("2001:db8:302::3", 'three.example.com.'), //
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
{%endhighlight%}
|
{%endhighlight%}
|
||||||
{% include endExample.html %}
|
{% include endExample.html %}
|
||||||
|
|
||||||
|
In the future we plan on adding a flag to `A()` which will insert
|
||||||
|
the correct PTR() record if the approprate `D(REV()` domain (i.e. `.arpa` domain) has been
|
||||||
|
defined.
|
||||||
|
@ -219,6 +219,10 @@ func mx(name string, prio uint16, target string) *rec {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ptr(name, target string) *rec {
|
||||||
|
return makeRec(name, target, "PTR")
|
||||||
|
}
|
||||||
|
|
||||||
func makeRec(name, target, typ string) *rec {
|
func makeRec(name, target, typ string) *rec {
|
||||||
return &rec{
|
return &rec{
|
||||||
Name: name,
|
Name: name,
|
||||||
@ -288,6 +292,11 @@ var tests = []*TestCase{
|
|||||||
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 Priority", mx("@", 7, "foo2.com."), mx("mail", 15, "foo3.com.")),
|
||||||
|
|
||||||
|
//PTR
|
||||||
|
tc("Empty"),
|
||||||
|
tc("Create PTR record", ptr("4", "foo.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),
|
||||||
|
@ -1,2 +1,4 @@
|
|||||||
$TTL 300
|
$TTL 300
|
||||||
@ IN SOA DEFAULT_NOT_SET. DEFAULT_NOT_SET. 2017041717 3600 600 604800 1440
|
@ IN SOA DEFAULT_NOT_SET. DEFAULT_NOT_SET. 2017070632 3600 600 604800 1440
|
||||||
|
IN NS ns1.otherdomain.tld.
|
||||||
|
IN NS ns2.otherdomain.tld.
|
||||||
|
@ -273,6 +273,11 @@ func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
|
|||||||
rec.Target = dnsutil.AddOrigin(rec.Target, domain.Name+".")
|
rec.Target = dnsutil.AddOrigin(rec.Target, domain.Name+".")
|
||||||
} else if rec.Type == "A" || rec.Type == "AAAA" {
|
} else if rec.Type == "A" || rec.Type == "AAAA" {
|
||||||
rec.Target = net.ParseIP(rec.Target).String()
|
rec.Target = net.ParseIP(rec.Target).String()
|
||||||
|
} else if rec.Type == "PTR" {
|
||||||
|
var err error
|
||||||
|
if rec.Name, err = transform.PtrNameMagic(rec.Name, domain.Name); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Populate FQDN:
|
// Populate FQDN:
|
||||||
rec.NameFQDN = dnsutil.AddOrigin(rec.Name, domain.Name)
|
rec.NameFQDN = dnsutil.AddOrigin(rec.Name, domain.Name)
|
||||||
|
@ -32,7 +32,7 @@ func ReverseDomainName(cidr string) (string, error) {
|
|||||||
}
|
}
|
||||||
toTrim = (total - bits) / 4
|
toTrim = (total - bits) / 4
|
||||||
} else {
|
} else {
|
||||||
return "", fmt.Errorf("Invalid mask bit size: %d", total)
|
return "", fmt.Errorf("Address is not IPv4 or IPv6: %v", cidr)
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.SplitN(base, ".", toTrim+1)
|
parts := strings.SplitN(base, ".", toTrim+1)
|
||||||
|
69
pkg/transform/ptr.go
Normal file
69
pkg/transform/ptr.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package transform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PtrNameMagic(name, domain string) (string, error) {
|
||||||
|
// Implement the PTR name magic. If the name is a properly formed
|
||||||
|
// IPv4 or IPv6 address, we replace it with the right string (i.e
|
||||||
|
// reverse it and truncate it).
|
||||||
|
|
||||||
|
// If the name is already in-addr.arpa or ipv6.arpa,
|
||||||
|
// make sure the domain matches.
|
||||||
|
if strings.HasSuffix(name, ".in-addr.arpa.") || strings.HasSuffix(name, ".ip6.arpa.") {
|
||||||
|
if strings.HasSuffix(name, "."+domain+".") {
|
||||||
|
return strings.TrimSuffix(name, "."+domain+"."), nil
|
||||||
|
} else {
|
||||||
|
return name, errors.Errorf("PTR record %v in wrong domain (%v)", name, domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the domain is .arpa, we do magic.
|
||||||
|
if strings.HasSuffix(domain, ".in-addr.arpa") {
|
||||||
|
return ipv4magic(name, domain)
|
||||||
|
} else if strings.HasSuffix(domain, ".ip6.arpa") {
|
||||||
|
return ipv6magic(name, domain)
|
||||||
|
} else {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ipv4magic(name, domain string) (string, error) {
|
||||||
|
// Not a valid IPv4 address. Leave it alone.
|
||||||
|
ip := net.ParseIP(name)
|
||||||
|
if ip == nil || ip.To4() == nil || !strings.Contains(name, ".") {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse it.
|
||||||
|
rev, err := ReverseDomainName(ip.String() + "/32")
|
||||||
|
if err != nil {
|
||||||
|
return name, err
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(rev, "."+domain) {
|
||||||
|
err = errors.Errorf("ERROR: PTR record %v in wrong IPv4 domain (%v)", name, domain)
|
||||||
|
}
|
||||||
|
return strings.TrimSuffix(rev, "."+domain), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func ipv6magic(name, domain string) (string, error) {
|
||||||
|
// Not a valid IPv6 address. Leave it alone.
|
||||||
|
ip := net.ParseIP(name)
|
||||||
|
if ip == nil || len(ip) != 16 || !strings.Contains(name, ":") {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse it.
|
||||||
|
rev, err := ReverseDomainName(ip.String() + "/128")
|
||||||
|
if err != nil {
|
||||||
|
return name, err
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(rev, "."+domain) {
|
||||||
|
err = errors.Errorf("ERROR: PTR record %v in wrong IPv6 domain (%v)", name, domain)
|
||||||
|
}
|
||||||
|
return strings.TrimSuffix(rev, "."+domain), err
|
||||||
|
}
|
77
pkg/transform/ptr_test.go
Normal file
77
pkg/transform/ptr_test.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package transform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ptrmagic(name, domain string, al int) (string, error)
|
||||||
|
|
||||||
|
func TestPtrMagic(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
domain string
|
||||||
|
output string
|
||||||
|
fail bool
|
||||||
|
}{
|
||||||
|
// Magic IPv4:
|
||||||
|
{"1.2.3.4", "3.2.1.in-addr.arpa", "4", false},
|
||||||
|
{"1.2.3.4", "2.1.in-addr.arpa", "4.3", false},
|
||||||
|
{"1.2.3.4", "1.in-addr.arpa", "4.3.2", false},
|
||||||
|
|
||||||
|
// No magic IPv4:
|
||||||
|
{"1", "2.3.4.in-addr.arpa", "1", false},
|
||||||
|
{"1.2", "3.4.in-addr.arpa", "1.2", false},
|
||||||
|
{"1.2.3", "4.in-addr.arpa", "1.2.3", false},
|
||||||
|
{"1.2.3.4", "in-addr.arpa", "1.2.3.4", false}, // Not supported, but it works.
|
||||||
|
|
||||||
|
// Magic IPv6:
|
||||||
|
{"1", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "1", false},
|
||||||
|
{"1.0", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "1.0", false},
|
||||||
|
{"1.0.0", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "1.0.0", false},
|
||||||
|
{"1.0.0.0", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "1.0.0.0", false},
|
||||||
|
{"1.0.0.0.0", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "1.0.0.0.0", false},
|
||||||
|
{"1.0.0.0.0.0", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "1.0.0.0.0.0", false},
|
||||||
|
{"1.0.0.0.0.0.0", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "1.0.0.0.0.0.0", false},
|
||||||
|
{"1.0.0.0.0.0.0.0", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "1.0.0.0.0.0.0.0", false},
|
||||||
|
{"1.0.0.0.0.0.0.0.0", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "1.0.0.0.0.0.0.0.0", false},
|
||||||
|
{"1.0.0.0.0.0.0.0.0.0", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "1.0.0.0.0.0.0.0.0.0", false},
|
||||||
|
{"1.0.0.0.0.0.0.0.0.0.0", "0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "1.0.0.0.0.0.0.0.0.0.0", false},
|
||||||
|
{"1.0.0.0.0.0.0.0.0.0.0.0", "0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "1.0.0.0.0.0.0.0.0.0.0.0", false},
|
||||||
|
{"1.0.0.0.0.0.0.0.0.0.0.0.0", "0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "1.0.0.0.0.0.0.0.0.0.0.0.0", false},
|
||||||
|
{"1.0.0.0.0.0.0.0.0.0.0.0.0.0", "0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "1.0.0.0.0.0.0.0.0.0.0.0.0.0", false},
|
||||||
|
|
||||||
|
// If it doesn't end in .arpa, the magic is disabled:
|
||||||
|
{"1.2.3.4", "example.com", "1.2.3.4", false},
|
||||||
|
{"1", "example.com", "1", false},
|
||||||
|
{"1.0.0.0", "example.com", "1.0.0.0", false},
|
||||||
|
{"1.0.0.0.0.0.0.0", "example.com", "1.0.0.0.0.0.0.0", false},
|
||||||
|
|
||||||
|
// User manually reversed addresses:
|
||||||
|
{"1.1.1.1.in-addr.arpa.", "1.1.in-addr.arpa", "1.1", false},
|
||||||
|
{"1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.",
|
||||||
|
"0.2.ip6.arpa", "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0", false},
|
||||||
|
|
||||||
|
// Error cases:
|
||||||
|
{"1.1.1.1.in-addr.arpa.", "2.2.in-addr.arpa", "", true},
|
||||||
|
{"1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.", "9.9.ip6.arpa", "", true},
|
||||||
|
{"3.3.3.3", "4.4.in-addr.arpa", "", true},
|
||||||
|
{"2001:db8::1", "9.9.ip6.arpa", "", true},
|
||||||
|
|
||||||
|
// These should be errors but we don't check for them at this time:
|
||||||
|
//{"blurg", "3.4.in-addr.arpa", "blurg", true},
|
||||||
|
//{"1", "3.4.in-addr.arpa", "1", true},
|
||||||
|
}
|
||||||
|
for _, tst := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%s %s", tst.name, tst.domain), func(t *testing.T) {
|
||||||
|
o, errs := PtrNameMagic(tst.name, tst.domain)
|
||||||
|
if errs != nil && !tst.fail {
|
||||||
|
t.Errorf("Got error but expected none (%v)", errs)
|
||||||
|
} else if errs == nil && tst.fail {
|
||||||
|
t.Errorf("Expected error but got none (%v)", o)
|
||||||
|
} else if errs == nil && o != tst.output {
|
||||||
|
t.Errorf("Got (%v) expected (%v)", o, tst.output)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user