blob: 9769d7ec3d3a9366b934383f736ebeb0a61cb002 [file] [log] [blame]
Jan Schär4a180222024-07-29 16:32:54 +02001package dns
2
3import (
4 "net/netip"
5 "strings"
6)
7
8// IsSubDomain returns true if child is the same as or a subdomain of parent.
9// Both names should be in canonical form.
10func IsSubDomain(parent, child string) bool {
11 offset := len(child) - len(parent)
12 if offset < 0 || child[offset:] != parent {
13 return false
14 }
15 if offset == 0 || parent == "." {
16 return true
17 }
18 if child[offset-1] != '.' {
19 return false
20 }
21 j := offset - 2
22 for j >= 0 && child[j] == '\\' {
23 j--
24 }
25 return (offset-j)%2 == 0
26}
27
28// SplitLastLabel splits off the last label of a domain name. For example,
29// "www.example.com." is split into "www.example." and "com".
30func SplitLastLabel(name string) (rest string, label string) {
31 labelEnd := len(name)
32 if labelEnd != 0 && name[labelEnd-1] == '.' {
33 labelEnd--
34 }
35 labelStart := labelEnd
36 for ; labelStart > 0; labelStart-- {
37 if name[labelStart-1] != '.' {
38 continue
39 }
40 j := labelStart - 2
41 for j >= 0 && name[j] == '\\' {
42 j--
43 }
44 if (labelStart-j)%2 != 0 {
45 continue
46 }
47 break
48 }
49 return name[:labelStart], name[labelStart:labelEnd]
50}
51
52// ParseReverse parses name as a reverse lookup name. If name is not a reverse
53// name, the returned IP is invalid. The second return value indicates how many
54// bits of the address are present. The third return value is true if there are
55// extra labels before the reverse name.
56func ParseReverse(name string) (ip netip.Addr, bits int, extra bool) {
57 if strings.HasSuffix(name, "in-addr.arpa.") {
58 var ip [4]uint8
59 field := 0
60 pos := len(name) - len("in-addr.arpa.") - 1
61 for pos >= 0 && field < 4 {
62 if name[pos] != '.' {
63 break
64 }
65 nextPos := pos - 1
66 for nextPos >= 0 && name[nextPos] >= '0' && name[nextPos] <= '9' {
67 nextPos--
68 }
69 val := 0
70 for valPos := nextPos + 1; valPos < pos; valPos++ {
71 val = val*10 + int(name[valPos]) - '0'
72 }
73 valLen := pos - nextPos - 1
74 if valLen == 0 || valLen > 3 || (valLen != 1 && name[nextPos+1] == '0') || val > 255 {
75 // Number is empty, or too long, or has leading zero, or is too large.
76 break
77 }
78 ip[field] = uint8(val)
79 field++
80 pos = nextPos
81 }
82 if pos >= 0 {
83 // We did not parse the entire name.
84 j := pos - 1
85 for j >= 0 && name[j] == '\\' {
86 j--
87 }
88 if name[pos] != '.' || (pos-j)%2 == 0 {
89 // The last label we parsed was not terminated by a non-escaped dot.
90 field--
91 if field < 0 {
92 return netip.Addr{}, 0, false
93 }
94 ip[field] = 0
95 }
96 }
97 return netip.AddrFrom4(ip), field * 8, pos >= 0
98 }
99
100 if strings.HasSuffix(name, "ip6.arpa.") {
101 var ip [16]uint8
102 field := 0
103 half := false
104
105 pos := len(name) - len("ip6.arpa.") - 1
106 for pos > 0 && field < 16 {
107 if name[pos] != '.' {
108 break
109 }
110 var nibble uint8
111 if name[pos-1] >= '0' && name[pos-1] <= '9' {
112 nibble = name[pos-1] - '0'
113 } else if name[pos-1] >= 'a' && name[pos-1] <= 'f' {
114 nibble = name[pos-1] - 'a' + 10
115 } else {
116 break
117 }
118 if half {
119 ip[field] |= nibble
120 field++
121 half = false
122 } else {
123 ip[field] = nibble << 4
124 half = true
125 }
126 pos -= 2
127 }
128 if pos >= 0 {
129 // We did not parse the entire name.
130 j := pos - 1
131 for j >= 0 && name[j] == '\\' {
132 j--
133 }
134 if name[pos] != '.' || (pos-j)%2 == 0 {
135 // The last label we parsed was not terminated by a non-escaped dot.
136 if half {
137 half = false
138 ip[field] = 0
139 } else {
140 half = true
141 field--
142 if field < 0 {
143 return netip.Addr{}, 0, false
144 }
145 ip[field] &= 0xf0
146 }
147 }
148 }
149 bits := field * 8
150 if half {
151 bits += 4
152 }
153 return netip.AddrFrom16(ip), bits, pos >= 0
154 }
155
156 return netip.Addr{}, 0, false
157}