Add support for plain DNS over 53 (udp/tcp)

Refectored the _dnsExchange to create reusable udpExchange & tcpExchange
Also added support for a "." forward rule
This commit is contained in:
Jeffrey Damick 2024-04-25 18:21:10 -04:00
parent cb80bf33e8
commit 09a330c33f
5 changed files with 361 additions and 58 deletions

View file

@ -434,46 +434,79 @@ func _dnsExchange(
if err != nil {
return DNSExchangeResponse{err: err}
}
udpAddr, err := net.ResolveUDPAddr("udp", serverAddress)
packet, rtt, err = udpExchange(proxy, serverAddress, relay, binQuery)
if err != nil {
return DNSExchangeResponse{err: err}
}
upstreamAddr := udpAddr
if relay != nil {
proxy.prepareForRelay(udpAddr.IP, udpAddr.Port, &binQuery)
upstreamAddr = relay.RelayUDPAddr
}
now := time.Now()
pc, err := net.DialUDP("udp", nil, upstreamAddr)
if err != nil {
return DNSExchangeResponse{err: err}
}
defer pc.Close()
if err := pc.SetDeadline(time.Now().Add(proxy.timeout)); err != nil {
return DNSExchangeResponse{err: err}
}
if _, err := pc.Write(binQuery); err != nil {
return DNSExchangeResponse{err: err}
}
packet = make([]byte, MaxDNSPacketSize)
length, err := pc.Read(packet)
if err != nil {
return DNSExchangeResponse{err: err}
}
rtt = time.Since(now)
packet = packet[:length]
} else {
binQuery, err := query.Pack()
if err != nil {
return DNSExchangeResponse{err: err}
}
tcpAddr, err := net.ResolveTCPAddr("tcp", serverAddress)
packet, rtt, err = tcpExchange(proxy, serverAddress, relay, binQuery)
if err != nil {
return DNSExchangeResponse{err: err}
}
}
msg := dns.Msg{}
if err := msg.Unpack(packet); err != nil {
return DNSExchangeResponse{err: err}
}
return DNSExchangeResponse{response: &msg, rtt: rtt, err: nil}
}
func udpExchange(proxy *Proxy, serverAddress string, relay *DNSCryptRelay, query []byte) ([]byte, time.Duration, error) {
var rtt time.Duration
packet := []byte{}
udpAddr, err := net.ResolveUDPAddr("udp", serverAddress)
if err != nil {
return packet, rtt, err
}
upstreamAddr := udpAddr
if relay != nil {
proxy.prepareForRelay(udpAddr.IP, udpAddr.Port, &query)
upstreamAddr = relay.RelayUDPAddr
}
now := time.Now()
lAddr, err := net.ResolveUDPAddr("udp", ":0")
if err != nil {
return packet, rtt, err
}
pc, err := net.ListenUDP("udp", lAddr)
if err != nil {
return packet, rtt, err
}
defer pc.Close()
if err = pc.SetReadBuffer(MaxDNSPacketSize); err != nil {
return packet, rtt, err
}
if err = pc.SetDeadline(time.Now().Add(proxy.timeout)); err != nil {
return packet, rtt, err
}
if _, err = pc.WriteToUDPAddrPort(query, upstreamAddr.AddrPort()); err != nil {
return packet, rtt, err
}
packet = make([]byte, MaxDNSPacketSize)
length, _, err := pc.ReadFromUDPAddrPort(packet)
if err != nil {
return packet, rtt, err
}
rtt = time.Since(now)
packet = packet[:length]
return packet, rtt, err
}
func tcpExchange(proxy *Proxy, serverAddress string, relay *DNSCryptRelay, query []byte) ([]byte, time.Duration, error) {
var rtt time.Duration
packet := []byte{}
tcpAddr, err := net.ResolveTCPAddr("tcp", serverAddress)
if err != nil {
return packet, rtt, err
}
upstreamAddr := tcpAddr
if relay != nil {
proxy.prepareForRelay(tcpAddr.IP, tcpAddr.Port, &binQuery)
proxy.prepareForRelay(tcpAddr.IP, tcpAddr.Port, &query)
upstreamAddr = relay.RelayTCPAddr
}
now := time.Now()
@ -485,28 +518,23 @@ func _dnsExchange(
pc, err = (*proxyDialer).Dial("tcp", tcpAddr.String())
}
if err != nil {
return DNSExchangeResponse{err: err}
return packet, rtt, err
}
defer pc.Close()
if err := pc.SetDeadline(time.Now().Add(proxy.timeout)); err != nil {
return DNSExchangeResponse{err: err}
return packet, rtt, err
}
binQuery, err = PrefixWithSize(binQuery)
query, err = PrefixWithSize(query)
if err != nil {
return DNSExchangeResponse{err: err}
return packet, rtt, err
}
if _, err := pc.Write(binQuery); err != nil {
return DNSExchangeResponse{err: err}
if _, err := pc.Write(query); err != nil {
return packet, rtt, err
}
packet, err = ReadPrefixed(&pc)
if err != nil {
return DNSExchangeResponse{err: err}
return packet, rtt, err
}
rtt = time.Since(now)
}
msg := dns.Msg{}
if err := msg.Unpack(packet); err != nil {
return DNSExchangeResponse{err: err}
}
return DNSExchangeResponse{response: &msg, rtt: rtt, err: nil}
return packet, rtt, err
}

View file

@ -0,0 +1,197 @@
package main
import (
"net"
"strings"
"sync"
"testing"
"time"
"github.com/jedisct1/dlog"
"github.com/miekg/dns"
"github.com/powerman/check"
)
func TestUDPExchangeIPv4(tt *testing.T) {
if testing.Verbose() {
dlog.SetLogLevel(dlog.SeverityDebug)
dlog.UseSyslog(false)
}
t := check.T(tt)
us, err := startServerUDP(t, "udp4", dns.HandlerFunc(FakeServer1))
t.Nil(err)
defer us.Shutdown()
var relay *DNSCryptRelay
proxy := Proxy{timeout: 2 * time.Second}
msg := dns.Msg{}
msg.SetQuestion("example.com.", dns.TypeA)
query, err := msg.Pack()
t.Nil(err)
serverAddr, err := toServerAddr(us)
t.Nil(err)
r, rtt, err := udpExchange(&proxy, serverAddr, relay, query)
t.Nil(err)
t.Must(len(r) > 0)
t.Must(rtt > 0)
resp := dns.Msg{}
resp.Unpack(r)
t.Must(resp.Rcode == dns.RcodeSuccess)
}
func TestUDPExchangeIPv6(tt *testing.T) {
if testing.Verbose() {
dlog.SetLogLevel(dlog.SeverityDebug)
dlog.UseSyslog(false)
}
t := check.T(tt)
us, err := startServerUDP(t, "udp6", dns.HandlerFunc(FakeServer1))
t.Nil(err)
defer us.Shutdown()
var relay *DNSCryptRelay
proxy := Proxy{timeout: 2 * time.Second}
msg := dns.Msg{}
msg.SetQuestion("example.com.", dns.TypeA)
query, err := msg.Pack()
t.Nil(err)
serverAddr, err := toServerAddr(us)
t.Nil(err)
r, rtt, err := udpExchange(&proxy, serverAddr, relay, query)
t.Nil(err)
t.Must(len(r) > 0)
t.Must(rtt > 0)
resp := dns.Msg{}
resp.Unpack(r)
t.Must(resp.Rcode == dns.RcodeSuccess)
}
func TestTCPExchangeIPv4(tt *testing.T) {
if testing.Verbose() {
dlog.SetLogLevel(dlog.SeverityDebug)
dlog.UseSyslog(false)
}
t := check.T(tt)
us, err := startServerTCP(t, "tcp4", dns.HandlerFunc(FakeServer1))
t.Nil(err)
defer us.Shutdown()
var relay *DNSCryptRelay
proxy := Proxy{timeout: 2 * time.Second, xTransport: &XTransport{proxyDialer: nil}}
msg := dns.Msg{}
msg.SetQuestion("example.com.", dns.TypeA)
query, err := msg.Pack()
t.Nil(err)
serverAddr, err := toServerAddr(us)
t.Nil(err)
r, rtt, err := tcpExchange(&proxy, serverAddr, relay, query)
t.Nil(err)
t.Must(len(r) > 0)
t.Must(rtt > 0)
resp := dns.Msg{}
resp.Unpack(r)
t.Must(resp.Rcode == dns.RcodeSuccess)
}
func TestTCPExchangeIPv6(tt *testing.T) {
if testing.Verbose() {
dlog.SetLogLevel(dlog.SeverityDebug)
dlog.UseSyslog(false)
}
t := check.T(tt)
us, err := startServerTCP(t, "tcp6", dns.HandlerFunc(FakeServer1))
t.Nil(err)
defer us.Shutdown()
var relay *DNSCryptRelay
proxy := Proxy{timeout: 2 * time.Second, xTransport: &XTransport{proxyDialer: nil}}
msg := dns.Msg{}
msg.SetQuestion("example.com.", dns.TypeA)
query, err := msg.Pack()
t.Nil(err)
serverAddr, err := toServerAddr(us)
t.Nil(err)
r, rtt, err := tcpExchange(&proxy, serverAddr, relay, query)
t.Nil(err)
t.Must(len(r) > 0)
t.Must(rtt > 0)
resp := dns.Msg{}
resp.Unpack(r)
t.Must(resp.Rcode == dns.RcodeSuccess)
}
func toServerAddr(s *dns.Server) (string, error) {
var h, p string
var err error
if strings.HasPrefix(s.Net, "udp") {
h, p, err = net.SplitHostPort(s.PacketConn.LocalAddr().String())
} else {
h, p, err = net.SplitHostPort(s.Listener.Addr().String())
}
if err != nil {
return "", err
}
if net.ParseIP(h).To4() == nil {
return "[::1]:" + p, nil
}
return "127.0.0.1:" + p, nil
}
func startServerUDP(t *check.C, proto string, h dns.Handler) (*dns.Server, error) {
waitLock := sync.Mutex{}
addr := ":0"
if proto == "udp6" {
addr = "[::]:0"
}
server := &dns.Server{Addr: addr, Net: proto, ReadTimeout: time.Hour, WriteTimeout: time.Hour, NotifyStartedFunc: waitLock.Unlock, Handler: h}
waitLock.Lock()
go func() {
err := server.ListenAndServe()
t.Nil(err)
}()
waitLock.Lock()
return server, nil
}
func startServerTCP(t *check.C, proto string, h dns.Handler) (*dns.Server, error) {
waitLock := sync.Mutex{}
addr := ":0"
if proto == "tcp6" {
addr = "[::]:0"
}
server := &dns.Server{Addr: addr, Net: proto, ReadTimeout: time.Hour, WriteTimeout: time.Hour, NotifyStartedFunc: waitLock.Unlock, Handler: h}
waitLock.Lock()
go func() {
err := server.ListenAndServe()
t.Nil(err)
}()
waitLock.Lock()
return server, nil
}
func FakeServer1(w dns.ResponseWriter, req *dns.Msg) {
m := new(dns.Msg)
m.SetReply(req)
m.Extra = make([]dns.RR, 1)
m.Extra[0] = &dns.TXT{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0}, Txt: []string{"Hello world"}}
w.WriteMsg(m)
}

View file

@ -94,6 +94,10 @@ func (plugin *PluginForward) Eval(pluginsState *PluginsState, msg *dns.Msg) erro
servers = candidate.servers
break
}
if "." == candidate.domain {
servers = candidate.servers
break
}
}
if len(servers) == 0 {
return nil

View file

@ -630,6 +630,10 @@ func (proxy *Proxy) processIncomingQuery(
if serverInfo != nil {
serverName = serverInfo.Name
needsEDNS0Padding = (serverInfo.Proto == stamps.StampProtoTypeDoH || serverInfo.Proto == stamps.StampProtoTypeTLS)
if serverInfo.Proto == stamps.StampProtoTypePlain && serverInfo.knownBugs.fragmentsBlocked {
pluginsState.maxPayloadSize = pluginsState.maxUnencryptedUDPSafePayloadSize
}
}
query, _ = pluginsState.ApplyQueryPlugins(&proxy.pluginsGlobals, query, needsEDNS0Padding)
if len(query) < MinDNSPacketSize || len(query) > MaxDNSPacketSize {
@ -786,6 +790,31 @@ func (proxy *Proxy) processIncomingQuery(
serverInfo.noticeFailure(proxy)
return response
}
} else if serverInfo.Proto == stamps.StampProtoTypePlain {
serverInfo.noticeBegin(proxy)
var relay *DNSCryptRelay
if serverInfo.Relay != nil && serverInfo.Relay.Dnscrypt != nil {
relay = serverInfo.Relay.Dnscrypt
}
truncated := false
if clientProto == "udp" {
response, _, err = udpExchange(proxy, serverInfo.UDPAddr.String(), relay, query)
if err != nil {
dlog.Warnf("Failed to exchange query with plain server [%v] %v", serverName, err)
response = nil
}
if len(response) >= MinDNSPacketSize {
truncated = HasTCFlag(response)
}
}
if truncated || clientProto == "tcp" {
response, _, err = tcpExchange(proxy, serverInfo.TCPAddr.String(), relay, query)
if err != nil {
dlog.Warnf("Failed to exchange query with plain server [%v] %v", serverName, err)
response = nil
}
}
} else {
dlog.Fatal("Unsupported protocol")
}

View file

@ -325,6 +325,8 @@ func fetchServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, isNew
return fetchDoHServerInfo(proxy, name, stamp, isNew)
} else if stamp.Proto == stamps.StampProtoTypeODoHTarget {
return fetchODoHTargetInfo(proxy, name, stamp, isNew)
} else if stamp.Proto == stamps.StampProtoTypePlain {
return fetchPlainServerInfo(proxy, name, stamp, isNew)
}
return ServerInfo{}, fmt.Errorf("Unsupported protocol for [%s]: [%s]", name, stamp.Proto.String())
}
@ -944,6 +946,49 @@ func fetchODoHTargetInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, is
return serverInfo, err
}
func fetchPlainServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, isNew bool) (ServerInfo, error) {
relay, err := route(proxy, name, stamp.Proto)
if err != nil {
return ServerInfo{}, err
}
if relay != nil {
return ServerInfo{}, errors.New("Relay is not supported by plain DNS")
}
remoteUDPAddr, err := net.ResolveUDPAddr("udp", stamp.ServerAddrStr)
if err != nil {
return ServerInfo{}, err
}
remoteTCPAddr, err := net.ResolveTCPAddr("tcp", stamp.ServerAddrStr)
if err != nil {
return ServerInfo{}, err
}
testQuery := dns.Msg{}
testQuery.SetQuestion(".", dns.TypeNS)
testQuery.Id = 0xcafe
testQuery.MsgHdr.RecursionDesired = true
in, rtt, fragmentsBlocked, err := DNSExchange(proxy, "udp", &testQuery, stamp.ServerAddrStr,
nil, &name, false)
if in != nil && in.Rcode != dns.RcodeSuccess {
dlog.Warnf("Plain Server Target Test Rcode: %v", in.Rcode)
}
if err != nil {
return ServerInfo{}, err
}
return ServerInfo{
Proto: stamps.StampProtoTypePlain,
Name: name,
Timeout: proxy.timeout,
UDPAddr: remoteUDPAddr,
TCPAddr: remoteTCPAddr,
Relay: relay,
initialRtt: int(rtt.Nanoseconds() / 1000000),
knownBugs: ServerBugs{fragmentsBlocked: fragmentsBlocked},
}, nil
}
func (serverInfo *ServerInfo) noticeFailure(proxy *Proxy) {
proxy.serversInfo.Lock()
serverInfo.rtt.Add(float64(proxy.timeout.Nanoseconds() / 1000000))