/
msg.go
220 lines (194 loc) · 6.27 KB
/
msg.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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
package ecscache
import (
"fmt"
"net"
"net/netip"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/golibs/netutil"
"github.com/miekg/dns"
)
// Message Utilities
//
// TODO(a.garipov): Consider adding some of these functions to dnsmsg.
// rmHopToHopData removes hop-to-top data, such as DNSSEC RRs, from resp.
// reqDO tells whether the request had the DNSSEC OK (DO) bit set.
func rmHopToHopData(resp *dns.Msg, qt dnsmsg.RRType, reqDO bool) {
// Filter out only DNSSEC RRs that aren't explicitly requested.
//
// See https://datatracker.ietf.org/doc/html/rfc4035#section-3.2.1 and
// https://github.com/AdguardTeam/dnsproxy/issues/144.
resp.Answer = rmHopToHopRRs(resp.Answer, reqDO, qt)
resp.Ns = rmHopToHopRRs(resp.Ns, reqDO, dns.TypeNone)
resp.Extra = rmHopToHopRRs(resp.Extra, reqDO, dns.TypeNone)
}
// rmHopToHopRRs removes OPT RRs unconditionally and removes DNSSEC RRs, with
// the exception of exc, if reqDO is false from rrs. It returns filtered,
// a slice which has the same underlying storage as rrs. The rest of rrs is
// filled with nils.
func rmHopToHopRRs(rrs []dns.RR, reqDO bool, exc uint16) (filtered []dns.RR) {
filtered = rrs[:0:len(rrs)]
for _, rr := range rrs {
rrType := rr.Header().Rrtype
if rrType != dns.TypeOPT && (reqDO || !isDNSSEC(rr) || rrType == exc) {
filtered = append(filtered, rr)
}
}
// Set the remaining items to nil to let the garbage collector do its job.
for i := len(filtered); i < len(rrs); i++ {
rrs[i] = nil
}
return filtered
}
// isDNSSEC returns true if rr is a DNSSEC RR. NSEC, NSEC3, DS, DNSKEY and
// RRSIG/SIG are DNSSEC records.
func isDNSSEC(rr dns.RR) (ok bool) {
switch rr.(type) {
case
*dns.NSEC,
*dns.NSEC3,
*dns.DS,
*dns.RRSIG,
*dns.SIG,
*dns.DNSKEY:
return true
default:
return false
}
}
// setRespAD sets the Authenticated Data (AD) bit based on request data.
// reqAD and reqDO tell whether the request had the Authenticated Data (AD) and
// DNSSEC OK (DO) bits set.
//
// Per RFC 6840, validating resolvers should only set the AD bit when a response
// both meets the conditions listed in RFC 4035 and the request contained either
// a set DO bit or a set AD bit.
func setRespAD(resp *dns.Msg, reqAD, reqDO bool) {
resp.AuthenticatedData = resp.AuthenticatedData && (reqAD || reqDO)
}
// setECS sets the EDNS Client Subnet option using data from ecs. Both msg and
// ecs must not be nil. ecsFam should be either [netutil.AddrFamilyIPv4] or
// [netutil.AddrFamilyIPv6]. ecs should contain an IP address of the same
// family as ecsFam.
func setECS(
msg *dns.Msg,
ecs *agd.ECS,
ecsFam netutil.AddrFamily,
isResp bool,
) (err error) {
ip, err := addrToNetIP(ecs.Subnet.Addr(), ecsFam)
if err != nil {
return fmt.Errorf("checking subnet ip: %w", err)
}
prefixLen := uint8(ecs.Subnet.Bits())
var scope uint8
if isResp {
scope = prefixLen
}
opt := msg.IsEdns0()
if opt == nil {
msg.SetEdns0(dnsmsg.DefaultEDNSUDPSize, !isResp || msg.AuthenticatedData)
opt = msg.Extra[len(msg.Extra)-1].(*dns.OPT)
} else {
opt.SetUDPSize(dnsmsg.DefaultEDNSUDPSize)
for _, o := range opt.Option {
if edns, ok := o.(*dns.EDNS0_SUBNET); ok {
edns.SourceNetmask = prefixLen
edns.SourceScope = scope
edns.Address = ip
return nil
}
}
}
opt.Option = append(opt.Option, &dns.EDNS0_SUBNET{
Code: dns.EDNS0SUBNET,
Family: uint16(ecsFam),
SourceNetmask: prefixLen,
SourceScope: scope,
Address: ip,
})
return nil
}
// addrToNetIP returns ip as a net.IP with the correct number of bytes for fam.
// fam must be either [netutil.AddrFamilyIPv4] or [netutil.AddrFamilyIPv6].
func addrToNetIP(ip netip.Addr, fam netutil.AddrFamily) (res net.IP, err error) {
switch fam {
case netutil.AddrFamilyIPv4:
if ip.Is6() {
return nil, fmt.Errorf("cannot convert %s to ipv4", ip)
}
// Use a temporary variable to make the value addressable. Don't use
// AsSlice, since that would return a 16-byte form of the address.
ip4 := ip.As4()
return ip4[:], nil
case netutil.AddrFamilyIPv6:
if ip.Is4() {
return nil, fmt.Errorf("bad ipv4 addr %s for ipv6 addr fam", ip)
}
return ip.AsSlice(), nil
default:
return nil, fmt.Errorf("unsupported addr fam %s", fam)
}
}
// isCacheable returns true if msg can be cached. It doesn't consider the TTL
// values of the records.
func isCacheable(msg *dns.Msg) (ok bool) {
if msg.Truncated || len(msg.Question) != 1 {
return false
}
switch msg.Rcode {
case dns.RcodeSuccess:
return isCacheableNOERROR(msg)
case
dns.RcodeNameError,
dns.RcodeServerFailure:
return true
default:
return false
}
}
// isCacheableNOERROR returns true if resp is a cacheable. resp should be
// a NOERROR response. resp is considered cacheable if either of the following
// is true:
//
// - it's a response to a request with the corresponding records present in
// the answer section; or
//
// - it's a valid NODATA response to an A or AAAA request with an SOA record
// in the authority section.
//
// TODO(a.garipov): Consider unifying with findLowestTTL. It would be nice to
// be able to extract all relevant information about the cacheability of
// a response with one iteration.
func isCacheableNOERROR(resp *dns.Msg) (ok bool) {
// Iterate through the answer section to find relevant records. Skip CNAME
// and SIG records, because a NODATA response may have either no records in
// the answer section at all or have only these types. Any other type of
// record means that this is neither a real response nor a NODATA response.
//
// See https://datatracker.ietf.org/doc/html/rfc2308#section-2.2.
qt := resp.Question[0].Qtype
for _, rr := range resp.Answer {
rrType := rr.Header().Rrtype
switch rrType {
case qt:
// This is a normal response to a question. Cache it.
return true
case dns.TypeCNAME, dns.TypeSIG:
// This could still be a NODATA response. Go on.
default:
// This is a weird, non-NODATA response. Don't cache it.
return false
}
}
// Find the SOA record in the authority section if there is one. If there
// isn't, this is not a cacheable NODATA response.
//
// See https://datatracker.ietf.org/doc/html/rfc2308#section-5.
for _, rr := range resp.Ns {
if _, ok = rr.(*dns.SOA); ok {
return true
}
}
return false
}