dns: Add even more hacks to AuthLoookupIPAddr, add tests

This commit is contained in:
fox.cpp 2020-11-27 22:56:29 +03:00
parent 72d92e5d20
commit 50c1caed35
No known key found for this signature in database
GPG key ID: 5B991F6215D2FCC0
2 changed files with 232 additions and 21 deletions

View file

@ -25,6 +25,7 @@ import (
"strings"
"time"
"github.com/foxcpp/maddy/framework/log"
"github.com/miekg/dns"
)
@ -210,18 +211,26 @@ func (e ExtResolver) AuthLookupIPAddr(ctx context.Context, host string) (ad bool
msg.AuthenticatedData = true
resp, err := e.exchange(ctx, msg)
aaaaFailed := false
var (
v6ad bool
v6addrs []net.IPAddr
)
if err != nil {
return false, nil, err
}
v6addrs := make([]net.IPAddr, 0, len(resp.Answer))
v6ad := resp.AuthenticatedData
for _, rr := range resp.Answer {
aaaaRR, ok := rr.(*dns.AAAA)
if !ok {
continue
// Disregard the error for AAAA lookups.
resp = &dns.Msg{}
aaaaFailed = true
log.DefaultLogger.Error("Network I/O error during AAAA lookup", err, "host", host)
} else {
v6addrs = make([]net.IPAddr, 0, len(resp.Answer))
v6ad = resp.AuthenticatedData
for _, rr := range resp.Answer {
aaaaRR, ok := rr.(*dns.AAAA)
if !ok {
continue
}
v6addrs = append(v6addrs, net.IPAddr{IP: aaaaRR.AAAA})
}
v6addrs = append(v6addrs, net.IPAddr{IP: aaaaRR.AAAA})
}
// Then repeat query with IPv4.
@ -231,18 +240,26 @@ func (e ExtResolver) AuthLookupIPAddr(ctx context.Context, host string) (ad bool
msg.AuthenticatedData = true
resp, err = e.exchange(ctx, msg)
var (
v4ad bool
v4addrs []net.IPAddr
)
if err != nil {
return false, nil, err
}
v4ad := resp.AuthenticatedData
v4addrs := make([]net.IPAddr, 0, len(resp.Answer))
for _, rr := range resp.Answer {
aRR, ok := rr.(*dns.A)
if !ok {
continue
if aaaaFailed {
return false, nil, err
}
// Disregard A lookup error if AAAA succeeded.
log.DefaultLogger.Error("Network I/O error during A lookup, using AAAA records", err, "host", host)
} else {
v4ad = resp.AuthenticatedData
v4addrs = make([]net.IPAddr, 0, len(resp.Answer))
for _, rr := range resp.Answer {
aRR, ok := rr.(*dns.A)
if !ok {
continue
}
v4addrs = append(v4addrs, net.IPAddr{IP: aRR.A})
}
v4addrs = append(v4addrs, net.IPAddr{IP: aRR.A})
}
// A little bit of careful handling is required if AD is inconsistent
@ -255,10 +272,10 @@ func (e ExtResolver) AuthLookupIPAddr(ctx context.Context, host string) (ad bool
addrs = append(addrs, v6addrs...)
addrs = append(addrs, v4addrs...)
} else {
addrs = append(addrs, v4addrs...)
if v6ad {
addrs = append(addrs, v6addrs...)
}
addrs = append(addrs, v4addrs...)
}
return v4ad, addrs, nil
}

View file

@ -0,0 +1,194 @@
package dns
import (
"context"
"fmt"
"net"
"reflect"
"strconv"
"testing"
"time"
"github.com/foxcpp/maddy/framework/log"
"github.com/miekg/dns"
)
type TestSrvAction int
const (
TestSrvTimeout TestSrvAction = iota
TestSrvServfail
TestSrvNoAddr
TestSrvOk
)
func (a TestSrvAction) String() string {
switch a {
case TestSrvTimeout:
return "SrvTimeout"
case TestSrvServfail:
return "SrvServfail"
case TestSrvNoAddr:
return "SrvNoAddr"
case TestSrvOk:
return "SrvOk"
default:
panic("wtf action")
}
}
type IPAddrTestServer struct {
udpServ dns.Server
aAction TestSrvAction
aAD bool
aaaaAction TestSrvAction
aaaaAD bool
}
func (s *IPAddrTestServer) Run() {
pconn, err := net.ListenPacket("udp4", "127.0.0.1:0")
if err != nil {
panic(err)
}
s.udpServ.PacketConn = pconn
s.udpServ.Handler = s
go s.udpServ.ActivateAndServe()
}
func (s *IPAddrTestServer) Close() {
s.udpServ.PacketConn.Close()
}
func (s *IPAddrTestServer) Addr() *net.UDPAddr {
return s.udpServ.PacketConn.LocalAddr().(*net.UDPAddr)
}
func (s *IPAddrTestServer) ServeDNS(w dns.ResponseWriter, m *dns.Msg) {
q := m.Question[0]
var (
act TestSrvAction
ad bool
)
switch q.Qtype {
case dns.TypeA:
act = s.aAction
ad = s.aAD
case dns.TypeAAAA:
act = s.aaaaAction
ad = s.aaaaAD
default:
panic("wtf qtype")
}
reply := new(dns.Msg)
reply.SetReply(m)
reply.RecursionAvailable = true
reply.AuthenticatedData = ad
switch act {
case TestSrvTimeout:
return // no nobody heard from him since...
case TestSrvServfail:
reply.Rcode = dns.RcodeServerFailure
case TestSrvNoAddr:
case TestSrvOk:
switch q.Qtype {
case dns.TypeA:
reply.Answer = append(reply.Answer, &dns.A{
Hdr: dns.RR_Header{
Name: q.Name,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 9999,
},
A: net.ParseIP("127.0.0.1"),
})
case dns.TypeAAAA:
reply.Answer = append(reply.Answer, &dns.AAAA{
Hdr: dns.RR_Header{
Name: q.Name,
Rrtype: dns.TypeAAAA,
Class: dns.ClassINET,
Ttl: 9999,
},
AAAA: net.ParseIP("::1"),
})
}
}
w.WriteMsg(reply)
}
func TestExtResolver_AuthLookupIPAddr(t *testing.T) {
// AuthLookupIPAddr has a rather convoluted logic for combined A/AAAA
// lookups that return the best-effort result and also has some nuanced in
// AD flag handling for use in DANE algorithms.
// Silence log messages about disregarded I/O errors.
log.DefaultLogger.Out = nil
test := func(aAct, aaaaAct TestSrvAction, aAD, aaaaAD bool, ad bool, addrs []net.IP, err bool) {
t.Helper()
t.Run(fmt.Sprintln(aAct, aaaaAct, aAD, aaaaAD), func(t *testing.T) {
t.Helper()
s := IPAddrTestServer{}
s.aAction = aAct
s.aaaaAction = aaaaAct
s.aAD = aAD
s.aaaaAD = aaaaAD
s.Run()
defer s.Close()
res := ExtResolver{
cl: new(dns.Client),
Cfg: &dns.ClientConfig{
Servers: []string{"127.0.0.1"},
Port: strconv.Itoa(s.Addr().Port),
Timeout: 1,
},
}
res.cl.Dialer = &net.Dialer{
Timeout: 500 * time.Millisecond,
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
actualAd, actualAddrs, actualErr := res.AuthLookupIPAddr(ctx, "maddy.test")
if (actualErr != nil) != err {
t.Fatal("actualErr:", actualErr, "expectedErr:", err)
}
if actualAd != ad {
t.Error("actualAd:", actualAd, "expectedAd:", ad)
}
ipAddrs := make([]net.IPAddr, 0, len(addrs))
if len(addrs) == 0 {
ipAddrs = nil // lookup returns nil addrs for error cases
}
for _, a := range addrs {
ipAddrs = append(ipAddrs, net.IPAddr{IP: a, Zone: ""})
}
if !reflect.DeepEqual(actualAddrs, ipAddrs) {
t.Logf("actualAddrs: %#+v", actualAddrs)
t.Logf("addrs: %#+v", ipAddrs)
t.Fail()
}
})
}
test(TestSrvOk, TestSrvOk, true, true, true, []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1").To4()}, false)
test(TestSrvOk, TestSrvOk, true, false, true, []net.IP{net.ParseIP("127.0.0.1").To4()}, false)
test(TestSrvOk, TestSrvOk, false, true, false, []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1").To4()}, false)
test(TestSrvOk, TestSrvOk, false, false, false, []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1").To4()}, false)
test(TestSrvOk, TestSrvTimeout, true, true, true, []net.IP{net.ParseIP("127.0.0.1").To4()}, false)
test(TestSrvOk, TestSrvServfail, true, true, true, []net.IP{net.ParseIP("127.0.0.1").To4()}, false)
test(TestSrvOk, TestSrvNoAddr, true, true, true, []net.IP{net.ParseIP("127.0.0.1").To4()}, false)
test(TestSrvNoAddr, TestSrvOk, true, true, true, []net.IP{net.ParseIP("::1")}, false)
test(TestSrvServfail, TestSrvServfail, true, true, false, nil, true)
// actualAd is false, we don't want to risk reporting positive AD result if
// something is wrong with IPv4 lookup.
test(TestSrvTimeout, TestSrvOk, true, true, false, []net.IP{net.ParseIP("::1")}, false)
test(TestSrvServfail, TestSrvOk, true, true, false, []net.IP{net.ParseIP("::1")}, false)
}