maddy/dns/dnssec.go
2019-10-18 21:50:34 +03:00

185 lines
4 KiB
Go

package dns
import (
"context"
"net"
"strings"
"time"
"github.com/miekg/dns"
)
// ExtResolver is a convenience wrapper for miekg/dns library that provides
// access to certain low-level functionality (notably, AD flag in responses,
// indicating whether DNSSEC verification was performed by the server).
type ExtResolver struct {
cl *dns.Client
cfg *dns.ClientConfig
}
func (e ExtResolver) exchange(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) {
var resp *dns.Msg
var lastErr error
for _, srv := range e.cfg.Servers {
resp, _, lastErr = e.cl.ExchangeContext(ctx, msg, net.JoinHostPort(srv, e.cfg.Port))
if lastErr == nil {
break
}
}
return resp, lastErr
}
func (e ExtResolver) AuthLookupAddr(ctx context.Context, addr string) (ad bool, names []string, err error) {
revAddr, err := dns.ReverseAddr(addr)
if err != nil {
return false, nil, err
}
msg := new(dns.Msg)
msg.SetQuestion(revAddr, dns.TypePTR)
msg.SetEdns0(4096, false)
msg.AuthenticatedData = true
resp, err := e.exchange(ctx, msg)
if err != nil {
return false, nil, err
}
ad = resp.AuthenticatedData
names = make([]string, 0, len(resp.Answer))
for _, rr := range resp.Answer {
ptrRR, ok := rr.(*dns.PTR)
if !ok {
continue
}
names = append(names, ptrRR.Ptr)
}
return
}
func (e ExtResolver) AuthLookupHost(ctx context.Context, host string) (ad bool, addrs []string, err error) {
ad, addrParsed, err := e.AuthLookupIPAddr(ctx, host)
if err != nil {
return false, nil, err
}
addrs = make([]string, 0, len(addrParsed))
for _, addr := range addrParsed {
addrs = append(addrs, addr.String())
}
return ad, addrs, nil
}
func (e ExtResolver) AuthLookupMX(ctx context.Context, name string) (ad bool, mxs []*net.MX, err error) {
msg := new(dns.Msg)
msg.SetQuestion(dns.Fqdn(name), dns.TypeMX)
msg.SetEdns0(4096, false)
msg.AuthenticatedData = true
resp, err := e.exchange(ctx, msg)
if err != nil {
return false, nil, err
}
ad = resp.AuthenticatedData
mxs = make([]*net.MX, 0, len(resp.Answer))
for _, rr := range resp.Answer {
mxRR, ok := rr.(*dns.MX)
if !ok {
continue
}
mxs = append(mxs, &net.MX{
Host: mxRR.Mx,
Pref: mxRR.Preference,
})
}
return
}
func (e ExtResolver) AuthLookupTXT(ctx context.Context, name string) (ad bool, recs []string, err error) {
msg := new(dns.Msg)
msg.SetQuestion(dns.Fqdn(name), dns.TypeTXT)
msg.SetEdns0(4096, false)
msg.AuthenticatedData = true
resp, err := e.exchange(ctx, msg)
if err != nil {
return false, nil, err
}
ad = resp.AuthenticatedData
recs = make([]string, 0, len(resp.Answer))
for _, rr := range resp.Answer {
txtRR, ok := rr.(*dns.TXT)
if !ok {
continue
}
recs = append(recs, strings.Join(txtRR.Txt, ""))
}
return
}
func (e ExtResolver) AuthLookupIPAddr(ctx context.Context, host string) (ad bool, addrs []net.IPAddr, err error) {
// First, query IPv6.
msg := new(dns.Msg)
msg.SetQuestion(dns.Fqdn(host), dns.TypeAAAA)
msg.SetEdns0(4096, false)
msg.AuthenticatedData = true
resp, err := e.exchange(ctx, msg)
if err != nil {
return false, nil, err
}
ad = resp.AuthenticatedData
addrs = make([]net.IPAddr, 0, len(resp.Answer))
for _, rr := range resp.Answer {
aRR, ok := rr.(*dns.A)
if !ok {
continue
}
addrs = append(addrs, net.IPAddr{IP: aRR.A})
}
// Then repeat query with IPv4.
msg = new(dns.Msg)
msg.SetQuestion(dns.Fqdn(host), dns.TypeA)
msg.SetEdns0(4096, false)
msg.AuthenticatedData = true
resp, err = e.exchange(ctx, msg)
if err != nil {
return false, nil, err
}
// Both queries should be authenticated.
ad = ad && resp.AuthenticatedData
for _, rr := range resp.Answer {
aaaaRR, ok := rr.(*dns.AAAA)
if !ok {
continue
}
addrs = append(addrs, net.IPAddr{IP: aaaaRR.AAAA})
}
return ad, addrs, err
}
func NewExtResolver() (*ExtResolver, error) {
cfg, err := dns.ClientConfigFromFile("/etc/resolv.conf")
if err != nil {
return nil, err
}
cl := new(dns.Client)
cl.Dialer = &net.Dialer{
Timeout: time.Duration(cfg.Timeout) * time.Second,
}
return &ExtResolver{
cl: cl,
cfg: cfg,
}, nil
}