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