From 85e7dddc9bf4c8a1b21d987b4aa3e0084e3d97ec Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Sat, 12 Dec 2020 23:09:15 +0100 Subject: [PATCH] Move a few DNS things to dnsutils --- dnscrypt-proxy/dnscrypt_certs.go | 222 +------------------------------ dnscrypt-proxy/dnsutils.go | 218 ++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+), 220 deletions(-) diff --git a/dnscrypt-proxy/dnscrypt_certs.go b/dnscrypt-proxy/dnscrypt_certs.go index 3f85bf79..14e52212 100644 --- a/dnscrypt-proxy/dnscrypt_certs.go +++ b/dnscrypt-proxy/dnscrypt_certs.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/binary" "errors" - "net" "strings" "time" @@ -45,7 +44,7 @@ func FetchCurrentDNSCryptCert(proxy *Proxy, serverName *string, proto string, pk if knownBugs.fragmentsBlocked { tryFragmentsSupport = false } - in, rtt, fragmentsBlocked, err := dnsExchange(proxy, proto, &query, serverAddress, relay, serverName, tryFragmentsSupport) + in, rtt, fragmentsBlocked, err := DNSExchange(proxy, proto, &query, serverAddress, relay, serverName, tryFragmentsSupport) if err != nil { dlog.Noticef("[%s] TIMEOUT", *serverName) return CertInfo{}, 0, fragmentsBlocked, err @@ -62,7 +61,7 @@ func FetchCurrentDNSCryptCert(proxy *Proxy, serverName *string, proto string, pk } else { txt = strings.Join(t.Txt, "") } - binCert := packTxtString(txt) + binCert := PackTXTRR(txt) if len(binCert) < 124 { dlog.Warnf("[%v] Certificate too short", *serverName) continue @@ -153,220 +152,3 @@ func FetchCurrentDNSCryptCert(proxy *Proxy, serverName *string, proto string, pk } return certInfo, int(rtt.Nanoseconds() / 1000000), fragmentsBlocked, nil } - -func isDigit(b byte) bool { return b >= '0' && b <= '9' } - -func dddToByte(s []byte) byte { - return byte((s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0')) -} - -func packTxtString(s string) []byte { - bs := make([]byte, len(s)) - msg := make([]byte, 0) - copy(bs, s) - for i := 0; i < len(bs); i++ { - if bs[i] == '\\' { - i++ - if i == len(bs) { - break - } - if i+2 < len(bs) && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) { - msg = append(msg, dddToByte(bs[i:])) - i += 2 - } else if bs[i] == 't' { - msg = append(msg, '\t') - } else if bs[i] == 'r' { - msg = append(msg, '\r') - } else if bs[i] == 'n' { - msg = append(msg, '\n') - } else { - msg = append(msg, bs[i]) - } - } else { - msg = append(msg, bs[i]) - } - } - return msg -} - -type dnsExchangeResponse struct { - response *dns.Msg - rtt time.Duration - priority int - fragmentsBlocked bool - err error -} - -func dnsExchange(proxy *Proxy, proto string, query *dns.Msg, serverAddress string, relay *DNSCryptRelay, serverName *string, tryFragmentsSupport bool) (*dns.Msg, time.Duration, bool, error) { - for { - cancelChannel := make(chan struct{}) - channel := make(chan dnsExchangeResponse) - var err error - options := 0 - - for tries := 0; tries < 3; tries++ { - if tryFragmentsSupport { - queryCopy := query.Copy() - queryCopy.Id += uint16(options) - go func(query *dns.Msg, delay time.Duration) { - option := _dnsExchange(proxy, proto, query, serverAddress, relay, 1500) - option.fragmentsBlocked = false - option.priority = 0 - channel <- option - time.Sleep(delay) - select { - case <-cancelChannel: - return - default: - } - }(queryCopy, time.Duration(200*tries)*time.Millisecond) - options++ - } - queryCopy := query.Copy() - queryCopy.Id += uint16(options) - go func(query *dns.Msg, delay time.Duration) { - option := _dnsExchange(proxy, proto, query, serverAddress, relay, 480) - option.fragmentsBlocked = true - option.priority = 1 - channel <- option - time.Sleep(delay) - select { - case <-cancelChannel: - return - default: - } - }(queryCopy, time.Duration(250*tries)*time.Millisecond) - options++ - } - var bestOption *dnsExchangeResponse - for i := 0; i < options; i++ { - if dnsExchangeResponse := <-channel; dnsExchangeResponse.err == nil { - if bestOption == nil || dnsExchangeResponse.priority < bestOption.priority || - (dnsExchangeResponse.priority == bestOption.priority && dnsExchangeResponse.rtt < bestOption.rtt) { - bestOption = &dnsExchangeResponse - if bestOption.priority == 0 { - close(cancelChannel) - break - } - } - } else { - err = dnsExchangeResponse.err - } - } - if bestOption != nil { - if bestOption.fragmentsBlocked { - dlog.Debugf("Certificate retrieval for [%v] succeeded but server is blocking fragments", *serverName) - } else { - dlog.Debugf("Certificate retrieval for [%v] succeeded", *serverName) - } - return bestOption.response, bestOption.rtt, bestOption.fragmentsBlocked, nil - } - - if relay == nil || !proxy.anonDirectCertFallback { - if err == nil { - err = errors.New("Unable to reach the server") - } - return nil, 0, false, err - } - dlog.Infof("Unable to get a certificate for [%v] via relay [%v], retrying over a direct connection", *serverName, relay.RelayUDPAddr.IP) - relay = nil - } -} - -func _dnsExchange(proxy *Proxy, proto string, query *dns.Msg, serverAddress string, relay *DNSCryptRelay, paddedLen int) dnsExchangeResponse { - var packet []byte - var rtt time.Duration - - if proto == "udp" { - qNameLen, padding := len(query.Question[0].Name), 0 - if qNameLen < paddedLen { - padding = paddedLen - qNameLen - } - if padding > 0 { - opt := new(dns.OPT) - opt.Hdr.Name = "." - ext := new(dns.EDNS0_PADDING) - ext.Padding = make([]byte, padding) - opt.Option = append(opt.Option, ext) - query.Extra = []dns.RR{opt} - } - binQuery, err := query.Pack() - if err != nil { - return dnsExchangeResponse{err: err} - } - udpAddr, err := net.ResolveUDPAddr("udp", serverAddress) - 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) - 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 { - return dnsExchangeResponse{err: err} - } - return dnsExchangeResponse{response: &msg, rtt: rtt, err: nil} -} diff --git a/dnscrypt-proxy/dnsutils.go b/dnscrypt-proxy/dnsutils.go index 548d19ce..de4e5f3a 100644 --- a/dnscrypt-proxy/dnsutils.go +++ b/dnscrypt-proxy/dnsutils.go @@ -8,6 +8,7 @@ import ( "time" "unicode/utf8" + "github.com/jedisct1/dlog" "github.com/miekg/dns" ) @@ -256,3 +257,220 @@ func removeEDNS0Options(msg *dns.Msg) bool { edns0.Option = []dns.EDNS0{} return true } + +func isDigit(b byte) bool { return b >= '0' && b <= '9' } + +func dddToByte(s []byte) byte { + return byte((s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0')) +} + +func PackTXTRR(s string) []byte { + bs := make([]byte, len(s)) + msg := make([]byte, 0) + copy(bs, s) + for i := 0; i < len(bs); i++ { + if bs[i] == '\\' { + i++ + if i == len(bs) { + break + } + if i+2 < len(bs) && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) { + msg = append(msg, dddToByte(bs[i:])) + i += 2 + } else if bs[i] == 't' { + msg = append(msg, '\t') + } else if bs[i] == 'r' { + msg = append(msg, '\r') + } else if bs[i] == 'n' { + msg = append(msg, '\n') + } else { + msg = append(msg, bs[i]) + } + } else { + msg = append(msg, bs[i]) + } + } + return msg +} + +type DNSExchangeResponse struct { + response *dns.Msg + rtt time.Duration + priority int + fragmentsBlocked bool + err error +} + +func DNSExchange(proxy *Proxy, proto string, query *dns.Msg, serverAddress string, relay *DNSCryptRelay, serverName *string, tryFragmentsSupport bool) (*dns.Msg, time.Duration, bool, error) { + for { + cancelChannel := make(chan struct{}) + channel := make(chan DNSExchangeResponse) + var err error + options := 0 + + for tries := 0; tries < 3; tries++ { + if tryFragmentsSupport { + queryCopy := query.Copy() + queryCopy.Id += uint16(options) + go func(query *dns.Msg, delay time.Duration) { + option := _dnsExchange(proxy, proto, query, serverAddress, relay, 1500) + option.fragmentsBlocked = false + option.priority = 0 + channel <- option + time.Sleep(delay) + select { + case <-cancelChannel: + return + default: + } + }(queryCopy, time.Duration(200*tries)*time.Millisecond) + options++ + } + queryCopy := query.Copy() + queryCopy.Id += uint16(options) + go func(query *dns.Msg, delay time.Duration) { + option := _dnsExchange(proxy, proto, query, serverAddress, relay, 480) + option.fragmentsBlocked = true + option.priority = 1 + channel <- option + time.Sleep(delay) + select { + case <-cancelChannel: + return + default: + } + }(queryCopy, time.Duration(250*tries)*time.Millisecond) + options++ + } + var bestOption *DNSExchangeResponse + for i := 0; i < options; i++ { + if dnsExchangeResponse := <-channel; dnsExchangeResponse.err == nil { + if bestOption == nil || dnsExchangeResponse.priority < bestOption.priority || + (dnsExchangeResponse.priority == bestOption.priority && dnsExchangeResponse.rtt < bestOption.rtt) { + bestOption = &dnsExchangeResponse + if bestOption.priority == 0 { + close(cancelChannel) + break + } + } + } else { + err = dnsExchangeResponse.err + } + } + if bestOption != nil { + if bestOption.fragmentsBlocked { + dlog.Debugf("[%v] public key retrieval succeeded but server is blocking fragments", *serverName) + } else { + dlog.Debugf("[%v] public key retrieval succeeded", *serverName) + } + return bestOption.response, bestOption.rtt, bestOption.fragmentsBlocked, nil + } + + if relay == nil || !proxy.anonDirectCertFallback { + if err == nil { + err = errors.New("Unable to reach the server") + } + return nil, 0, false, err + } + dlog.Infof("Unable to get the public key for [%v] via relay [%v], retrying over a direct connection", *serverName, relay.RelayUDPAddr.IP) + relay = nil + } +} + +func _dnsExchange(proxy *Proxy, proto string, query *dns.Msg, serverAddress string, relay *DNSCryptRelay, paddedLen int) DNSExchangeResponse { + var packet []byte + var rtt time.Duration + + if proto == "udp" { + qNameLen, padding := len(query.Question[0].Name), 0 + if qNameLen < paddedLen { + padding = paddedLen - qNameLen + } + if padding > 0 { + opt := new(dns.OPT) + opt.Hdr.Name = "." + ext := new(dns.EDNS0_PADDING) + ext.Padding = make([]byte, padding) + opt.Option = append(opt.Option, ext) + query.Extra = []dns.RR{opt} + } + binQuery, err := query.Pack() + if err != nil { + return DNSExchangeResponse{err: err} + } + udpAddr, err := net.ResolveUDPAddr("udp", serverAddress) + 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) + 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 { + return DNSExchangeResponse{err: err} + } + return DNSExchangeResponse{response: &msg, rtt: rtt, err: nil} +}