/
dns64.go
204 lines (175 loc) · 5.84 KB
/
dns64.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
// Package dns64 implements a plugin that performs DNS64.
//
// See: RFC 6147 (https://tools.ietf.org/html/rfc6147)
package dns64
import (
"context"
"errors"
"net"
"time"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/metrics"
"github.com/coredns/coredns/plugin/pkg/nonwriter"
"github.com/coredns/coredns/plugin/pkg/response"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
// UpstreamInt wraps the Upstream API for dependency injection during testing
type UpstreamInt interface {
Lookup(ctx context.Context, state request.Request, name string, typ uint16) (*dns.Msg, error)
}
// DNS64 performs DNS64.
type DNS64 struct {
Next plugin.Handler
Prefix *net.IPNet
TranslateAll bool // Not comply with 5.1.1
Upstream UpstreamInt
}
// ServeDNS implements the plugin.Handler interface.
func (d *DNS64) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
// Don't proxy if we don't need to.
if !requestShouldIntercept(&request.Request{W: w, Req: r}) {
return d.Next.ServeDNS(ctx, w, r)
}
// Pass the request to the next plugin in the chain, but intercept the response.
nw := nonwriter.New(w)
origRc, origErr := d.Next.ServeDNS(ctx, nw, r)
if nw.Msg == nil { // somehow we didn't get a response (or raw bytes were written)
return origRc, origErr
}
// If the response doesn't need DNS64, short-circuit.
if !d.responseShouldDNS64(nw.Msg) {
w.WriteMsg(nw.Msg)
return origRc, origErr
}
// otherwise do the actual DNS64 request and response synthesis
msg, err := d.DoDNS64(ctx, w, r, nw.Msg)
if err != nil {
// err means we weren't able to even issue the A request
// to CoreDNS upstream
return dns.RcodeServerFailure, err
}
RequestsTranslatedCount.WithLabelValues(metrics.WithServer(ctx)).Inc()
w.WriteMsg(msg)
return msg.MsgHdr.Rcode, nil
}
// Name implements the Handler interface.
func (d *DNS64) Name() string { return "dns64" }
// requestShouldIntercept returns true if the request represents one that is eligible
// for DNS64 rewriting:
// 1. The request came in over IPv6 (not in RFC)
// 2. The request is of type AAAA
// 3. The request is of class INET
func requestShouldIntercept(req *request.Request) bool {
// Only intercept with this when the request came in over IPv6. This is not mentioned in the RFC.
// File an issue if you think we should translate even requests made using IPv4, or have a configuration flag
if req.Family() == 1 { // If it came in over v4, don't do anything.
return false
}
// Do not modify if question is not AAAA or not of class IN. See RFC 6147 5.1
return req.QType() == dns.TypeAAAA && req.QClass() == dns.ClassINET
}
// responseShouldDNS64 returns true if the response indicates we should attempt
// DNS64 rewriting:
// 1. The response has no valid (RFC 5.1.4) AAAA records (RFC 5.1.1)
// 2. The response code (RCODE) is not 3 (Name Error) (RFC 5.1.2)
//
// Note that requestShouldIntercept must also have been true, so the request
// is known to be of type AAAA.
func (d *DNS64) responseShouldDNS64(origResponse *dns.Msg) bool {
ty, _ := response.Typify(origResponse, time.Now().UTC())
// Handle NameError normally. See RFC 6147 5.1.2
// All other error types are "equivalent" to empty response
if ty == response.NameError {
return false
}
// If we've configured to always translate, well, then always translate.
if d.TranslateAll {
return true
}
// if response includes AAAA record, no need to rewrite
for _, rr := range origResponse.Answer {
if rr.Header().Rrtype == dns.TypeAAAA {
return false
}
}
return true
}
// DoDNS64 takes an (empty) response to an AAAA question, issues the A request,
// and synthesizes the answer. Returns the response message, or error on internal failure.
func (d *DNS64) DoDNS64(ctx context.Context, w dns.ResponseWriter, r *dns.Msg, origResponse *dns.Msg) (*dns.Msg, error) {
req := request.Request{W: w, Req: r} // req is unused
resp, err := d.Upstream.Lookup(ctx, req, req.Name(), dns.TypeA)
if err != nil {
return nil, err
}
out := d.Synthesize(r, origResponse, resp)
return out, nil
}
// Synthesize merges the AAAA response and the records from the A response
func (d *DNS64) Synthesize(origReq, origResponse, resp *dns.Msg) *dns.Msg {
ret := dns.Msg{}
ret.SetReply(origReq)
// 5.3.2: DNS64 MUST pass the additional section unchanged
ret.Extra = resp.Extra
ret.Ns = resp.Ns
// 5.1.7: The TTL is the minimum of the A RR and the SOA RR. If SOA is
// unknown, then the TTL is the minimum of A TTL and 600
SOATtl := uint32(600) // Default NS record TTL
for _, ns := range origResponse.Ns {
if ns.Header().Rrtype == dns.TypeSOA {
SOATtl = ns.Header().Ttl
}
}
ret.Answer = make([]dns.RR, 0, len(resp.Answer))
// convert A records to AAAA records
for _, rr := range resp.Answer {
header := rr.Header()
// 5.3.3: All other RR's MUST be returned unchanged
if header.Rrtype != dns.TypeA {
ret.Answer = append(ret.Answer, rr)
continue
}
aaaa, _ := to6(d.Prefix, rr.(*dns.A).A)
// ttl is min of SOA TTL and A TTL
ttl := SOATtl
if rr.Header().Ttl < ttl {
ttl = rr.Header().Ttl
}
// Replace A answer with a DNS64 AAAA answer
ret.Answer = append(ret.Answer, &dns.AAAA{
Hdr: dns.RR_Header{
Name: header.Name,
Rrtype: dns.TypeAAAA,
Class: header.Class,
Ttl: ttl,
},
AAAA: aaaa,
})
}
return &ret
}
// to6 takes a prefix and IPv4 address and returns an IPv6 address according to RFC 6052.
func to6(prefix *net.IPNet, addr net.IP) (net.IP, error) {
addr = addr.To4()
if addr == nil {
return nil, errors.New("not a valid IPv4 address")
}
n, _ := prefix.Mask.Size()
// Assumes prefix has been validated during setup
v6 := make([]byte, 16)
i, j := 0, 0
for ; i < n/8; i++ {
v6[i] = prefix.IP[i]
}
for ; i < 8; i, j = i+1, j+1 {
v6[i] = addr[j]
}
if i == 8 {
i++
}
for ; j < 4; i, j = i+1, j+1 {
v6[i] = addr[j]
}
return v6, nil
}