mirror of
https://github.com/DNSCrypt/dnscrypt-proxy.git
synced 2025-04-04 21:57:44 +03:00
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:
parent
cb80bf33e8
commit
09a330c33f
5 changed files with 361 additions and 58 deletions
|
@ -434,75 +434,19 @@ 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}
|
||||
}
|
||||
upstreamAddr := tcpAddr
|
||||
if relay != nil {
|
||||
proxy.prepareForRelay(tcpAddr.IP, tcpAddr.Port, &binQuery)
|
||||
upstreamAddr = relay.RelayTCPAddr
|
||||
}
|
||||
now := time.Now()
|
||||
var pc net.Conn
|
||||
proxyDialer := proxy.xTransport.proxyDialer
|
||||
if proxyDialer == nil {
|
||||
pc, err = net.DialTCP("tcp", nil, upstreamAddr)
|
||||
} else {
|
||||
pc, err = (*proxyDialer).Dial("tcp", tcpAddr.String())
|
||||
}
|
||||
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}
|
||||
}
|
||||
binQuery, err = PrefixWithSize(binQuery)
|
||||
if err != nil {
|
||||
return DNSExchangeResponse{err: err}
|
||||
}
|
||||
if _, err := pc.Write(binQuery); err != nil {
|
||||
return DNSExchangeResponse{err: err}
|
||||
}
|
||||
packet, err = ReadPrefixed(&pc)
|
||||
if err != nil {
|
||||
return DNSExchangeResponse{err: err}
|
||||
}
|
||||
rtt = time.Since(now)
|
||||
}
|
||||
msg := dns.Msg{}
|
||||
if err := msg.Unpack(packet); err != nil {
|
||||
|
@ -510,3 +454,87 @@ func _dnsExchange(
|
|||
}
|
||||
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, &query)
|
||||
upstreamAddr = relay.RelayTCPAddr
|
||||
}
|
||||
now := time.Now()
|
||||
var pc net.Conn
|
||||
proxyDialer := proxy.xTransport.proxyDialer
|
||||
if proxyDialer == nil {
|
||||
pc, err = net.DialTCP("tcp", nil, upstreamAddr)
|
||||
} else {
|
||||
pc, err = (*proxyDialer).Dial("tcp", tcpAddr.String())
|
||||
}
|
||||
if err != nil {
|
||||
return packet, rtt, err
|
||||
}
|
||||
defer pc.Close()
|
||||
if err := pc.SetDeadline(time.Now().Add(proxy.timeout)); err != nil {
|
||||
return packet, rtt, err
|
||||
}
|
||||
query, err = PrefixWithSize(query)
|
||||
if err != nil {
|
||||
return packet, rtt, err
|
||||
}
|
||||
if _, err := pc.Write(query); err != nil {
|
||||
return packet, rtt, err
|
||||
}
|
||||
packet, err = ReadPrefixed(&pc)
|
||||
if err != nil {
|
||||
return packet, rtt, err
|
||||
}
|
||||
rtt = time.Since(now)
|
||||
return packet, rtt, err
|
||||
}
|
||||
|
|
197
dnscrypt-proxy/dnsutils_test.go
Normal file
197
dnscrypt-proxy/dnsutils_test.go
Normal 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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue