189 lines
4.9 KiB
Go
189 lines
4.9 KiB
Go
// Package dns64 implements a plugin that performs DNS64.
|
|
package dns64
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/coredns/coredns/plugin"
|
|
"github.com/coredns/coredns/plugin/pkg/nonwriter"
|
|
"github.com/coredns/coredns/plugin/pkg/response"
|
|
"github.com/coredns/coredns/plugin/pkg/upstream"
|
|
"github.com/coredns/coredns/request"
|
|
"github.com/coredns/proxy"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
// DNS64 performs DNS64.
|
|
type DNS64 struct {
|
|
*proxy.Proxy
|
|
NativeUpstream *upstream.Upstream
|
|
Next plugin.Handler
|
|
Prefix *net.IPNet
|
|
TranslateAll bool // Not comply with 5.1.1
|
|
}
|
|
|
|
// ServeDNS implements the plugin.Handler interface.
|
|
func (d DNS64) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
|
hijackedWriter := &ResponseWriter{w, d, ctx, r}
|
|
return d.Next.ServeDNS(ctx, hijackedWriter, r)
|
|
}
|
|
|
|
// Name implements the Handler interface.
|
|
func (d DNS64) Name() string { return "dns64" }
|
|
|
|
// ResponseWriter is a response writer that implements DNS64, when an AAAA question returns
|
|
// NODATA, it will try and fetch any A records and synthesize the AAAA records on the fly.
|
|
type ResponseWriter struct {
|
|
dns.ResponseWriter
|
|
DNS64
|
|
ctx context.Context
|
|
req *dns.Msg
|
|
}
|
|
|
|
// WriteMsg implements the dns.ResponseWriter interface.
|
|
func (r *ResponseWriter) WriteMsg(res *dns.Msg) error {
|
|
state := request.Request{W: r, Req: res}
|
|
|
|
// Only respond 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 state.Family() == 1 { // If it came in over v4, don't do anything.
|
|
return r.ResponseWriter.WriteMsg(res)
|
|
}
|
|
|
|
// Do not modify if question is not AAAA or not of class IN. See 5.1
|
|
if state.QType() != dns.TypeAAAA || state.QClass() != dns.ClassINET {
|
|
return r.ResponseWriter.WriteMsg(res)
|
|
}
|
|
|
|
ty, _ := response.Typify(res, time.Now().UTC())
|
|
|
|
// Handle NameError normally. See 5.1.2
|
|
if ty == response.NameError {
|
|
return r.ResponseWriter.WriteMsg(res)
|
|
}
|
|
|
|
// If results in no error and has AAAA, handle normally. See 5.1.6
|
|
if ty == response.NoError {
|
|
// TranslateAll will disable this behaviour and translate all queries. See 5.1.1
|
|
if hasAAAA(res) && !r.TranslateAll {
|
|
return r.ResponseWriter.WriteMsg(res)
|
|
}
|
|
}
|
|
|
|
// Perform Lookup
|
|
var replacement *dns.Msg
|
|
var err error
|
|
if len(*r.Upstreams) > 0 {
|
|
// Use Proxy to lookup
|
|
req := new(dns.Msg)
|
|
req.SetQuestion(state.Name(), dns.TypeA)
|
|
nw := nonwriter.New(state.W)
|
|
_, err = r.Proxy.ServeDNS(r.ctx, nw, req)
|
|
replacement = nw.Msg
|
|
} else {
|
|
// Use NativeUpstream to lookup
|
|
replacement, err = r.NativeUpstream.Lookup(r.ctx, state, state.Name(), dns.TypeA)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Modify response.
|
|
r.AnswerRewrite(res, replacement)
|
|
return r.ResponseWriter.WriteMsg(res)
|
|
}
|
|
|
|
// Write implements the dns.ResponseWriter interface.
|
|
func (r *ResponseWriter) Write(buf []byte) (int, error) {
|
|
log.Warning("[WARNING] DNS64 called with Write: not performing DNS64")
|
|
n, err := r.ResponseWriter.Write(buf)
|
|
return n, err
|
|
}
|
|
|
|
// Hijack implements the dns.ResponseWriter interface.
|
|
func (r *ResponseWriter) Hijack() {
|
|
r.ResponseWriter.Hijack()
|
|
return
|
|
}
|
|
|
|
// AnswerRewrite turns A responses into AAAA responses
|
|
func (dns64 DNS64) AnswerRewrite(r *dns.Msg, replacement *dns.Msg) {
|
|
r.MsgHdr.Rcode = dns.RcodeSuccess
|
|
// Extract TTLs
|
|
nsTtl := uint32(600) // Default NS record TTL
|
|
for i := 0; i < len(r.Ns); i++ {
|
|
if r.Ns[i].Header().Rrtype == dns.TypeSOA {
|
|
nsTtl = r.Ns[i].Header().Ttl // Use specified NS record TTL
|
|
}
|
|
}
|
|
// Replace all our AAAA answers with A answers
|
|
r.Answer = replacement.Answer
|
|
for i := 0; i < len(r.Answer); i++ {
|
|
ans := r.Answer[i]
|
|
hdr := ans.Header()
|
|
// All of the answers should be A answers. Ensure that
|
|
if hdr.Rrtype == dns.TypeA {
|
|
aaaa, _ := to6(dns64.Prefix, ans.(*dns.A).A)
|
|
ttl := nsTtl
|
|
// Limit the TTL to the NS record TTL
|
|
if ans.Header().Ttl < ttl {
|
|
ttl = ans.Header().Ttl
|
|
}
|
|
// Replace A answer with a DNS64 AAAA answer
|
|
r.Answer[i] = &dns.AAAA{
|
|
Hdr: dns.RR_Header{
|
|
Name: hdr.Name,
|
|
Rrtype: dns.TypeAAAA,
|
|
Class: hdr.Class,
|
|
Ttl: ttl,
|
|
},
|
|
AAAA: aaaa,
|
|
}
|
|
}
|
|
}
|
|
// TODO: Explain this
|
|
r.Ns = []dns.RR{}
|
|
}
|
|
|
|
// hasAAAA checks if AAAA records exists in dns.Msg
|
|
func hasAAAA(res *dns.Msg) bool {
|
|
for _, a := range res.Answer {
|
|
if a.Header().Rrtype == dns.TypeAAAA {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// 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
|
|
}
|