forked from StackExchange/dnscontrol
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ptr.go
118 lines (101 loc) · 3.3 KB
/
ptr.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package transform
import (
"fmt"
"net"
"regexp"
"strconv"
"strings"
"github.com/pkg/errors"
)
// PtrNameMagic implements the PTR magic.
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
}
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
}
result := strings.TrimSuffix(rev, "."+domain)
// Are we in the right domain?
if strings.HasSuffix(rev, "."+domain) {
return result, nil
}
if ipMatchesClasslessDomain(ip, domain) {
return strings.SplitN(rev, ".", 2)[0], nil
}
return "", errors.Errorf("PTR record %v in wrong IPv4 domain (%v)", name, domain)
}
var isRfc2317Format1 = regexp.MustCompile(`(\d{1,3})/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.in-addr\.arpa$`)
// ipMatchesClasslessDomain returns true if ip is appropriate for domain.
// domain is a reverse DNS lookup zone (in-addr.arpa) as described in RFC2317.
func ipMatchesClasslessDomain(ip net.IP, domain string) bool {
// The unofficial but preferred format in RFC2317:
m := isRfc2317Format1.FindStringSubmatch(domain)
if m != nil {
// IP: Domain:
// 172.20.18.27 128/27.18.20.172.in-addr.arpa
// A B C D F M X Y Z
// The following should be true:
// A==Z, B==Y, C==X.
// If you mask ip by M, the last octet should be F.
ii := ip.To4()
a, b, c, _ := ii[0], ii[1], ii[2], ii[3]
f, m, x, y, z := atob(m[1]), atob(m[2]), atob(m[3]), atob(m[4]), atob(m[5])
masked := ip.Mask(net.CIDRMask(int(m), 32))
if a == z && b == y && c == x && masked.Equal(net.IPv4(a, b, c, f)) {
return true
}
}
// To extend this to include other formats, add them here.
return false
}
// atob converts a to a byte value or panics.
func atob(s string) byte {
if i, err := strconv.Atoi(s); err == nil {
if i < 256 {
return byte(i)
}
}
panic(fmt.Sprintf("(%v) matched \\d{1,3} but is not a byte", s))
}
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("PTR record %v in wrong IPv6 domain (%v)", name, domain)
}
return strings.TrimSuffix(rev, "."+domain), err
}