dnscrypt-proxy/dnscrypt-proxy/xtransport.go

323 lines
9.4 KiB
Go
Raw Normal View History

2018-01-30 15:51:07 +01:00
package main
import (
"bytes"
"context"
"crypto/sha512"
"crypto/tls"
"encoding/base64"
"encoding/hex"
2018-01-30 15:51:07 +01:00
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"strconv"
2018-01-30 15:51:07 +01:00
"strings"
"sync"
"time"
2018-04-18 18:47:10 +02:00
2018-01-30 15:51:07 +01:00
"github.com/jedisct1/dlog"
2018-04-18 18:58:39 +02:00
stamps "github.com/jedisct1/go-dnsstamps"
2018-01-30 15:51:07 +01:00
"github.com/miekg/dns"
"golang.org/x/net/http2"
2018-06-06 15:54:51 +02:00
netproxy "golang.org/x/net/proxy"
2018-01-30 15:51:07 +01:00
)
const DefaultFallbackResolver = "9.9.9.9:53"
2018-01-30 15:51:07 +01:00
type CachedIPs struct {
sync.RWMutex
cache map[string]string
}
type XTransport struct {
transport *http.Transport
keepAlive time.Duration
timeout time.Duration
cachedIPs CachedIPs
fallbackResolver string
mainProto string
ignoreSystemDNS bool
useIPv4 bool
useIPv6 bool
tlsDisableSessionTickets bool
tlsCipherSuite []uint16
2018-06-06 15:54:51 +02:00
proxyDialer *netproxy.Dialer
httpProxyFunction func(*http.Request) (*url.URL, error)
2018-01-30 15:51:07 +01:00
}
var DefaultKeepAlive = 5 * time.Second
var DefaultTimeout = 30 * time.Second
func NewXTransport() *XTransport {
2018-01-30 15:51:07 +01:00
xTransport := XTransport{
cachedIPs: CachedIPs{cache: make(map[string]string)},
keepAlive: DefaultKeepAlive,
timeout: DefaultTimeout,
fallbackResolver: DefaultFallbackResolver,
mainProto: "",
ignoreSystemDNS: false,
useIPv4: true,
useIPv6: false,
tlsDisableSessionTickets: false,
tlsCipherSuite: nil,
2018-01-30 15:51:07 +01:00
}
return &xTransport
}
func (xTransport *XTransport) clearCache() {
xTransport.cachedIPs.Lock()
xTransport.cachedIPs.cache = make(map[string]string)
xTransport.cachedIPs.Unlock()
dlog.Info("IP cache cleared")
}
func (xTransport *XTransport) rebuildTransport() {
dlog.Debug("Rebuilding transport")
if xTransport.transport != nil {
(*xTransport.transport).CloseIdleConnections()
}
timeout := xTransport.timeout
2018-01-30 15:51:07 +01:00
transport := &http.Transport{
DisableKeepAlives: false,
DisableCompression: true,
MaxIdleConns: 1,
IdleConnTimeout: xTransport.keepAlive,
2018-01-30 15:51:07 +01:00
ResponseHeaderTimeout: timeout,
ExpectContinueTimeout: timeout,
MaxResponseHeaderBytes: 4096,
DialContext: func(ctx context.Context, network, addrStr string) (net.Conn, error) {
2018-04-14 15:03:21 +02:00
host, port := ExtractHostAndPort(addrStr, stamps.DefaultPort)
2018-01-30 15:51:07 +01:00
ipOnly := host
xTransport.cachedIPs.RLock()
cachedIP := xTransport.cachedIPs.cache[host]
xTransport.cachedIPs.RUnlock()
if len(cachedIP) > 0 {
ipOnly = cachedIP
} else {
dlog.Debugf("[%s] IP address was not cached", host)
}
addrStr = ipOnly + ":" + strconv.Itoa(port)
2018-06-06 15:54:51 +02:00
if xTransport.proxyDialer == nil {
dialer := &net.Dialer{Timeout: timeout, KeepAlive: timeout, DualStack: true}
return dialer.DialContext(ctx, network, addrStr)
}
2019-06-26 19:51:57 +02:00
return (*xTransport.proxyDialer).Dial(network, addrStr)
2018-01-30 15:51:07 +01:00
},
}
if xTransport.httpProxyFunction != nil {
transport.Proxy = xTransport.httpProxyFunction
}
if xTransport.tlsDisableSessionTickets || xTransport.tlsCipherSuite != nil {
tlsClientConfig := tls.Config{
SessionTicketsDisabled: xTransport.tlsDisableSessionTickets,
}
if !xTransport.tlsDisableSessionTickets {
tlsClientConfig.ClientSessionCache = tls.NewLRUClientSessionCache(10)
}
if xTransport.tlsCipherSuite != nil {
tlsClientConfig.PreferServerCipherSuites = false
tlsClientConfig.CipherSuites = xTransport.tlsCipherSuite
}
transport.TLSClientConfig = &tlsClientConfig
}
http2.ConfigureTransport(transport)
2018-01-30 15:51:07 +01:00
xTransport.transport = transport
}
func (xTransport *XTransport) resolveUsingSystem(host string) (*string, error) {
foundIPs, err := net.LookupHost(host)
if err != nil {
return nil, err
}
for _, ip := range foundIPs {
foundIP := net.ParseIP(ip)
if foundIP == nil {
continue
}
if xTransport.useIPv4 {
if ipv4 := foundIP.To4(); ipv4 != nil {
foundIPx := foundIP.String()
return &foundIPx, nil
}
}
if xTransport.useIPv6 {
if ipv6 := foundIP.To16(); ipv6 != nil {
foundIPx := "[" + foundIP.String() + "]"
return &foundIPx, nil
}
}
}
return nil, err
}
func (xTransport *XTransport) resolveUsingResolver(dnsClient *dns.Client, host string, resolver string) (*string, error) {
var err error
if xTransport.useIPv4 {
msg := new(dns.Msg)
msg.SetQuestion(dns.Fqdn(host), dns.TypeA)
msg.SetEdns0(uint16(MaxDNSPacketSize), true)
var in *dns.Msg
in, _, err = dnsClient.Exchange(msg, resolver)
if err == nil {
for _, answer := range in.Answer {
if answer.Header().Rrtype == dns.TypeA {
foundIP := answer.(*dns.A).A.String()
return &foundIP, nil
}
}
}
}
if xTransport.useIPv6 {
msg := new(dns.Msg)
msg.SetQuestion(dns.Fqdn(host), dns.TypeAAAA)
msg.SetEdns0(uint16(MaxDNSPacketSize), true)
var in *dns.Msg
in, _, err = dnsClient.Exchange(msg, resolver)
if err == nil {
for _, answer := range in.Answer {
if answer.Header().Rrtype == dns.TypeAAAA {
foundIP := "[" + answer.(*dns.AAAA).AAAA.String() + "]"
return &foundIP, nil
}
}
}
}
return nil, err
}
func (xTransport *XTransport) Fetch(method string, url *url.URL, accept string, contentType string, body *[]byte, timeout time.Duration, padding *string) (*http.Response, time.Duration, error) {
2018-01-30 15:51:07 +01:00
if timeout <= 0 {
timeout = xTransport.timeout
}
client := http.Client{Transport: xTransport.transport, Timeout: timeout}
header := map[string][]string{"User-Agent": {"dnscrypt-proxy"}}
if len(accept) > 0 {
header["Accept"] = []string{accept}
}
if len(contentType) > 0 {
header["Content-Type"] = []string{contentType}
}
if padding != nil {
header["X-Pad"] = []string{*padding}
}
if body != nil {
h := sha512.Sum512(*body)
qs := url.Query()
qs.Add("body_hash", hex.EncodeToString(h[:32]))
url2 := *url
url2.RawQuery = qs.Encode()
url = &url2
}
2018-01-30 15:51:07 +01:00
req := &http.Request{
Method: method,
URL: url,
Header: header,
Close: false,
}
if body != nil {
req.ContentLength = int64(len(*body))
bc := ioutil.NopCloser(bytes.NewReader(*body))
req.Body = bc
2018-01-30 15:51:07 +01:00
}
var err error
host, _ := ExtractHostAndPort(url.Host, 0)
if xTransport.proxyDialer == nil && strings.HasSuffix(host, ".onion") {
return nil, 0, errors.New("Onion service is not reachable without Tor")
}
2019-04-14 11:18:14 +02:00
resolveByProxy := false
if xTransport.proxyDialer != nil || xTransport.httpProxyFunction != nil {
2019-04-14 11:18:14 +02:00
resolveByProxy = true
}
var foundIP *string
if !resolveByProxy && net.ParseIP(host) == nil {
xTransport.cachedIPs.RLock()
cachedIP := xTransport.cachedIPs.cache[host]
xTransport.cachedIPs.RUnlock()
if len(cachedIP) > 0 {
foundIP = &cachedIP
} else {
if !xTransport.ignoreSystemDNS {
foundIP, err = xTransport.resolveUsingSystem(host)
} else {
dlog.Debug("Ignoring system DNS")
}
if xTransport.ignoreSystemDNS || err != nil {
if xTransport.ignoreSystemDNS {
dlog.Debugf("Resolving [%s] using fallback resolver [%s]", host, xTransport.fallbackResolver)
} else {
dlog.Noticef("System DNS configuration not usable yet, exceptionally resolving [%s] using fallback resolver [%s]", host, xTransport.fallbackResolver)
}
dnsClient := dns.Client{Net: xTransport.mainProto}
foundIP, err = xTransport.resolveUsingResolver(&dnsClient, host, xTransport.fallbackResolver)
}
if foundIP == nil {
return nil, 0, fmt.Errorf("No IP found for [%s]", host)
}
if err != nil {
return nil, 0, err
}
xTransport.cachedIPs.Lock()
xTransport.cachedIPs.cache[host] = *foundIP
xTransport.cachedIPs.Unlock()
dlog.Debugf("[%s] IP address [%s] added to the cache", host, *foundIP)
}
2018-01-30 15:51:07 +01:00
}
start := time.Now()
resp, err := client.Do(req)
rtt := time.Since(start)
2018-01-30 15:51:07 +01:00
if err == nil {
if resp == nil {
err = errors.New("Webserver returned an error")
} else if resp.StatusCode < 200 || resp.StatusCode > 299 {
err = fmt.Errorf("Webserver returned code %d", resp.StatusCode)
}
} else {
(*xTransport.transport).CloseIdleConnections()
2018-01-30 15:51:07 +01:00
}
if err != nil {
dlog.Debugf("[%s]: [%s]", req.URL, err)
if xTransport.tlsCipherSuite != nil && strings.Contains(err.Error(), "handshake failure") {
dlog.Warnf("TLS handshake failure - Try changing or deleting the tls_cipher_suite value in the configuration file")
xTransport.tlsCipherSuite = nil
xTransport.rebuildTransport()
}
}
2018-01-30 15:51:07 +01:00
return resp, rtt, err
}
func (xTransport *XTransport) Get(url *url.URL, accept string, timeout time.Duration) (*http.Response, time.Duration, error) {
return xTransport.Fetch("GET", url, accept, "", nil, timeout, nil)
2018-01-30 15:51:07 +01:00
}
func (xTransport *XTransport) Post(url *url.URL, accept string, contentType string, body *[]byte, timeout time.Duration, padding *string) (*http.Response, time.Duration, error) {
return xTransport.Fetch("POST", url, accept, contentType, body, timeout, padding)
2018-01-30 15:51:07 +01:00
}
func (xTransport *XTransport) DoHQuery(useGet bool, url *url.URL, body []byte, timeout time.Duration) (*http.Response, time.Duration, error) {
padLen := 63 - (len(body)+63)&63
padding := xTransport.makePad(padLen)
2018-05-19 02:39:32 +02:00
dataType := "application/dns-message"
if useGet {
qs := url.Query()
qs.Add("ct", "")
encBody := base64.RawURLEncoding.EncodeToString(body)
qs.Add("dns", encBody)
url2 := *url
url2.RawQuery = qs.Encode()
return xTransport.Get(&url2, dataType, timeout)
}
return xTransport.Post(url, dataType, dataType, &body, timeout, padding)
}
func (xTransport *XTransport) makePad(padLen int) *string {
if padLen <= 0 {
return nil
}
padding := strings.Repeat("X", padLen)
return &padding
}