maddy/internal/dns/dnssec.go
fox.cpp 48e21f566e
Extend .debug.* flags and hide them by default
Allow to override DNS resolver address via the -debug.dnsoverride flag
and SMTP port via -debug.smtpport.

All flags are not available unless maddy is built using the 'debugflags'
tag.
2019-12-13 17:31:35 +03:00

195 lines
4.2 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 {
aaaaRR, ok := rr.(*dns.AAAA)
if !ok {
continue
}
addrs = append(addrs, net.IPAddr{IP: aaaaRR.AAAA})
}
// 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 {
aRR, ok := rr.(*dns.A)
if !ok {
continue
}
addrs = append(addrs, net.IPAddr{IP: aRR.A})
}
return ad, addrs, err
}
func NewExtResolver() (*ExtResolver, error) {
cfg, err := dns.ClientConfigFromFile("/etc/resolv.conf")
if err != nil {
return nil, err
}
if overrideServ != "" && overrideServ != "system-default" {
host, port, err := net.SplitHostPort(overrideServ)
if err != nil {
panic(err)
}
cfg.Servers = []string{host}
cfg.Port = port
}
cl := new(dns.Client)
cl.Dialer = &net.Dialer{
Timeout: time.Duration(cfg.Timeout) * time.Second,
}
return &ExtResolver{
cl: cl,
Cfg: cfg,
}, nil
}