2018-01-30 15:51:07 +01:00
package main
import (
"bytes"
"context"
2018-11-22 18:09:27 +01:00
"crypto/sha512"
2018-04-07 22:23:29 +02:00
"crypto/tls"
2018-02-05 11:30:10 +01:00
"encoding/base64"
2018-11-22 18:09:27 +01:00
"encoding/hex"
2018-01-30 15:51:07 +01:00
"errors"
"io/ioutil"
2019-10-20 17:09:14 +02:00
"math/rand"
2018-01-30 15:51:07 +01:00
"net"
"net/http"
"net/url"
2018-03-28 12:38:17 +02:00
"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"
2018-04-07 22:23:29 +02:00
"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
)
2019-10-20 16:24:02 +02:00
const (
DefaultFallbackResolver = "9.9.9.9:53"
DefaultKeepAlive = 5 * time . Second
DefaultTimeout = 30 * time . Second
2019-11-17 20:30:00 +01:00
SystemResolverIPTTL = 24 * time . Hour
2019-11-17 20:30:59 +01:00
MinResolverIPTTL = 12 * time . Hour
2019-12-09 17:08:59 +01:00
ExpiredCachedIPGraceTTL = 15 * time . Minute
2019-10-20 16:24:02 +02:00
)
2018-01-30 15:51:07 +01:00
2019-10-20 17:09:14 +02:00
type CachedIPItem struct {
2019-10-21 18:40:47 +02:00
ip net . IP
expiration * time . Time
2019-10-20 17:09:14 +02:00
}
2018-01-30 15:51:07 +01:00
type CachedIPs struct {
sync . RWMutex
2019-10-20 17:09:14 +02:00
cache map [ string ] * CachedIPItem
2018-01-30 15:51:07 +01:00
}
type XTransport struct {
2018-04-07 22:23:29 +02:00
transport * http . Transport
keepAlive time . Duration
timeout time . Duration
cachedIPs CachedIPs
fallbackResolver string
2019-07-10 13:13:28 +02:00
mainProto string
2018-04-07 22:23:29 +02:00
ignoreSystemDNS bool
useIPv4 bool
useIPv6 bool
tlsDisableSessionTickets bool
tlsCipherSuite [ ] uint16
2018-06-06 15:54:51 +02:00
proxyDialer * netproxy . Dialer
2018-11-15 18:47:33 +01:00
httpProxyFunction func ( * http . Request ) ( * url . URL , error )
2018-01-30 15:51:07 +01:00
}
2018-04-07 22:33:11 +02:00
func NewXTransport ( ) * XTransport {
2019-11-01 23:06:42 +01:00
if err := isIPAndPort ( DefaultFallbackResolver ) ; err != nil {
2019-10-20 20:35:25 +02:00
panic ( "DefaultFallbackResolver does not parse" )
}
2018-01-30 15:51:07 +01:00
xTransport := XTransport {
2019-10-20 17:09:14 +02:00
cachedIPs : CachedIPs { cache : make ( map [ string ] * CachedIPItem ) } ,
2018-04-07 22:23:29 +02:00
keepAlive : DefaultKeepAlive ,
2018-04-07 22:33:11 +02:00
timeout : DefaultTimeout ,
2018-04-07 22:23:29 +02:00
fallbackResolver : DefaultFallbackResolver ,
2019-07-10 13:13:28 +02:00
mainProto : "" ,
2019-11-17 20:30:00 +01:00
ignoreSystemDNS : true ,
2018-04-07 22:33:11 +02:00
useIPv4 : true ,
useIPv6 : false ,
2018-04-07 22:23:29 +02:00
tlsDisableSessionTickets : false ,
tlsCipherSuite : nil ,
2018-01-30 15:51:07 +01:00
}
2018-02-05 19:03:04 +01:00
return & xTransport
}
2019-10-20 21:30:16 +02:00
func ParseIP ( ipStr string ) net . IP {
return net . ParseIP ( strings . TrimRight ( strings . TrimLeft ( ipStr , "[" ) , "]" ) )
}
2019-10-20 17:09:14 +02:00
// If ttl < 0, never expire
2019-11-17 20:00:34 +01:00
// Otherwise, ttl is set to max(ttl, MinResolverIPTTL)
2019-10-20 17:09:14 +02:00
func ( xTransport * XTransport ) saveCachedIP ( host string , ip net . IP , ttl time . Duration ) {
2019-10-21 18:40:47 +02:00
item := & CachedIPItem { ip : ip , expiration : nil }
2019-10-20 17:09:14 +02:00
if ttl >= 0 {
2019-11-17 20:00:34 +01:00
if ttl < MinResolverIPTTL {
ttl = MinResolverIPTTL
2019-10-20 17:09:14 +02:00
}
2019-10-21 18:40:47 +02:00
expiration := time . Now ( ) . Add ( ttl )
item . expiration = & expiration
2019-10-20 17:09:14 +02:00
}
2019-10-21 18:36:47 +02:00
xTransport . cachedIPs . Lock ( )
2019-10-20 17:09:14 +02:00
xTransport . cachedIPs . cache [ host ] = item
2018-03-21 09:32:35 +01:00
xTransport . cachedIPs . Unlock ( )
2019-10-20 17:09:14 +02:00
}
2019-11-01 22:55:06 +01:00
func ( xTransport * XTransport ) loadCachedIP ( host string ) ( ip net . IP , expired bool ) {
2019-11-01 23:19:07 +01:00
ip , expired = nil , false
2019-10-21 18:36:47 +02:00
xTransport . cachedIPs . RLock ( )
2019-10-20 17:09:14 +02:00
item , ok := xTransport . cachedIPs . cache [ host ]
2019-10-21 18:36:47 +02:00
xTransport . cachedIPs . RUnlock ( )
2019-10-20 17:09:14 +02:00
if ! ok {
2019-11-01 22:55:06 +01:00
return
2019-10-20 17:09:14 +02:00
}
2019-11-01 22:55:06 +01:00
ip = item . ip
2019-10-21 18:40:47 +02:00
expiration := item . expiration
2019-11-01 22:55:06 +01:00
if expiration != nil && time . Until ( * expiration ) < 0 {
expired = true
2019-10-20 17:09:14 +02:00
}
2019-11-01 22:55:06 +01:00
return
2018-03-21 09:32:35 +01:00
}
2018-02-05 19:03:04 +01:00
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 ,
2018-04-02 01:49:09 +02:00
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
2019-12-09 17:03:16 +01:00
// resolveAndUpdateCache() is always called in `Fetch()` before the `Dial()`
2019-11-01 23:19:07 +01:00
// method is used, so that a cached entry must be present at this point.
2019-11-01 22:55:06 +01:00
cachedIP , _ := xTransport . loadCachedIP ( host )
2019-11-02 01:20:28 +01:00
if cachedIP != nil {
2019-10-31 16:38:43 +01:00
if ipv4 := cachedIP . To4 ( ) ; ipv4 != nil {
ipOnly = ipv4 . String ( )
} else {
ipOnly = "[" + cachedIP . String ( ) + "]"
}
2018-01-30 15:51:07 +01:00
} else {
dlog . Debugf ( "[%s] IP address was not cached" , host )
}
2018-03-28 12:38:17 +02:00
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
} ,
}
2018-11-15 18:47:33 +01:00
if xTransport . httpProxyFunction != nil {
transport . Proxy = xTransport . httpProxyFunction
}
2018-04-07 22:23:29 +02:00
if xTransport . tlsDisableSessionTickets || xTransport . tlsCipherSuite != nil {
tlsClientConfig := tls . Config {
SessionTicketsDisabled : xTransport . tlsDisableSessionTickets ,
}
2018-04-10 00:36:55 +02:00
if ! xTransport . tlsDisableSessionTickets {
tlsClientConfig . ClientSessionCache = tls . NewLRUClientSessionCache ( 10 )
}
2018-04-07 22:23:29 +02:00
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
}
2019-10-20 17:09:14 +02:00
func ( xTransport * XTransport ) resolveUsingSystem ( host string ) ( ip net . IP , ttl time . Duration , err error ) {
2019-11-17 20:30:00 +01:00
ttl = SystemResolverIPTTL
2019-10-20 17:09:14 +02:00
var foundIPs [ ] string
foundIPs , err = net . LookupHost ( host )
2019-04-14 13:46:07 +02:00
if err != nil {
2019-10-20 17:09:14 +02:00
return
2019-04-14 13:46:07 +02:00
}
2019-10-20 17:09:14 +02:00
ips := make ( [ ] net . IP , 0 )
2019-04-14 13:46:07 +02:00
for _ , ip := range foundIPs {
2019-10-20 17:09:14 +02:00
if foundIP := net . ParseIP ( ip ) ; foundIP != nil {
if xTransport . useIPv4 {
if ipv4 := foundIP . To4 ( ) ; ipv4 != nil {
ips = append ( ips , foundIP )
}
2019-04-14 13:46:07 +02:00
}
2019-10-20 17:09:14 +02:00
if xTransport . useIPv6 {
if ipv6 := foundIP . To16 ( ) ; ipv6 != nil {
ips = append ( ips , foundIP )
}
2019-04-14 13:46:07 +02:00
}
}
}
2019-10-20 17:09:14 +02:00
if len ( ips ) > 0 {
ip = ips [ rand . Intn ( len ( ips ) ) ]
}
return
2019-04-14 13:46:07 +02:00
}
2019-10-20 17:09:14 +02:00
func ( xTransport * XTransport ) resolveUsingResolver ( proto , host string , resolver string ) ( ip net . IP , ttl time . Duration , err error ) {
dnsClient := dns . Client { Net : proto }
2018-03-21 10:03:05 +01:00
if xTransport . useIPv4 {
2019-12-11 14:02:56 +01:00
msg := dns . Msg { }
2018-03-21 10:03:05 +01:00
msg . SetQuestion ( dns . Fqdn ( host ) , dns . TypeA )
2019-10-16 11:47:43 +02:00
msg . SetEdns0 ( uint16 ( MaxDNSPacketSize ) , true )
2018-03-21 10:03:05 +01:00
var in * dns . Msg
2019-12-11 14:02:56 +01:00
if in , _ , err = dnsClient . Exchange ( & msg , resolver ) ; err == nil {
2019-10-20 17:09:14 +02:00
answers := make ( [ ] dns . RR , 0 )
2018-03-21 10:03:05 +01:00
for _ , answer := range in . Answer {
if answer . Header ( ) . Rrtype == dns . TypeA {
2019-10-20 17:09:14 +02:00
answers = append ( answers , answer )
2018-03-21 10:03:05 +01:00
}
}
2019-10-20 17:09:14 +02:00
if len ( answers ) > 0 {
answer := answers [ rand . Intn ( len ( answers ) ) ]
ip = answer . ( * dns . A ) . A
ttl = time . Duration ( answer . Header ( ) . Ttl ) * time . Second
return
}
2018-03-21 10:03:05 +01:00
}
}
2019-10-03 18:43:27 +02:00
if xTransport . useIPv6 {
2019-12-11 14:02:56 +01:00
msg := dns . Msg { }
2018-03-21 10:03:05 +01:00
msg . SetQuestion ( dns . Fqdn ( host ) , dns . TypeAAAA )
2019-10-16 11:47:43 +02:00
msg . SetEdns0 ( uint16 ( MaxDNSPacketSize ) , true )
2018-03-21 10:03:05 +01:00
var in * dns . Msg
2019-12-11 14:02:56 +01:00
if in , _ , err = dnsClient . Exchange ( & msg , resolver ) ; err == nil {
2019-10-20 17:09:14 +02:00
answers := make ( [ ] dns . RR , 0 )
2018-03-21 10:03:05 +01:00
for _ , answer := range in . Answer {
if answer . Header ( ) . Rrtype == dns . TypeAAAA {
2019-10-20 17:09:14 +02:00
answers = append ( answers , answer )
2018-03-21 10:03:05 +01:00
}
}
2019-10-20 17:09:14 +02:00
if len ( answers ) > 0 {
answer := answers [ rand . Intn ( len ( answers ) ) ]
ip = answer . ( * dns . AAAA ) . AAAA
ttl = time . Duration ( answer . Header ( ) . Ttl ) * time . Second
return
}
}
}
return
}
2019-12-09 17:03:16 +01:00
// If a name is not present in the cache, resolve the name and update the cache
func ( xTransport * XTransport ) resolveAndUpdateCache ( host string ) error {
2019-10-20 17:09:14 +02:00
if xTransport . proxyDialer != nil || xTransport . httpProxyFunction != nil {
2019-12-09 16:59:02 +01:00
return nil
2019-10-20 17:09:14 +02:00
}
if ParseIP ( host ) != nil {
2019-12-09 16:59:02 +01:00
return nil
2019-10-20 17:09:14 +02:00
}
2019-11-01 22:55:06 +01:00
cachedIP , expired := xTransport . loadCachedIP ( host )
2019-11-02 01:50:35 +01:00
if cachedIP != nil && ! expired {
2019-12-09 16:59:02 +01:00
return nil
2019-10-20 17:09:14 +02:00
}
var foundIP net . IP
var ttl time . Duration
2019-12-09 16:59:02 +01:00
var err error
2019-10-20 17:09:14 +02:00
if ! xTransport . ignoreSystemDNS {
foundIP , ttl , err = xTransport . resolveUsingSystem ( host )
}
if xTransport . ignoreSystemDNS || err != nil {
protos := [ ] string { "udp" , "tcp" }
if xTransport . mainProto == "tcp" {
protos = [ ] string { "tcp" , "udp" }
}
for _ , proto := range protos {
if err != nil {
2019-11-17 22:04:58 +01:00
dlog . Noticef ( "System DNS configuration not usable yet, exceptionally resolving [%s] using resolver [%s] over %s" , host , xTransport . fallbackResolver , proto )
2019-10-20 17:09:14 +02:00
} else {
dlog . Debugf ( "Resolving [%s] using resolver %s[%s]" , host , proto , xTransport . fallbackResolver )
}
foundIP , ttl , err = xTransport . resolveUsingResolver ( proto , host , xTransport . fallbackResolver )
if err == nil {
break
}
2018-03-21 10:03:05 +01:00
}
}
2019-11-17 22:00:08 +01:00
if err != nil && xTransport . ignoreSystemDNS {
dlog . Noticef ( "Fallback resolver [%v] didn't respond - Trying with the system resolver as a last resort" , xTransport . fallbackResolver )
foundIP , ttl , err = xTransport . resolveUsingSystem ( host )
}
2019-11-17 20:00:34 +01:00
if ttl < MinResolverIPTTL {
ttl = MinResolverIPTTL
2019-11-02 02:01:03 +01:00
}
2019-10-20 17:09:14 +02:00
if err != nil {
2019-11-01 22:55:06 +01:00
if cachedIP != nil {
2019-12-09 17:08:59 +01:00
dlog . Noticef ( "Using stale [%v] cached address for a grace period" , host )
2019-11-01 22:55:06 +01:00
foundIP = cachedIP
2019-11-01 23:19:07 +01:00
ttl = ExpiredCachedIPGraceTTL
2019-11-01 22:55:06 +01:00
} else {
2019-12-09 16:59:02 +01:00
return err
2019-11-01 22:55:06 +01:00
}
2019-10-20 17:09:14 +02:00
}
xTransport . saveCachedIP ( host , foundIP , ttl )
2019-11-02 02:01:03 +01:00
dlog . Debugf ( "[%s] IP address [%s] added to the cache, valid for %v" , host , foundIP , ttl )
2019-12-09 16:59:02 +01:00
return nil
2018-03-21 10:03:05 +01:00
}
2019-12-22 17:34:16 +01:00
func ( xTransport * XTransport ) Fetch ( method string , url * url . URL , accept string , contentType string , body * [ ] byte , timeout time . Duration ) ( * 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 }
}
2018-11-22 18:09:27 +01:00
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
}
2019-10-16 12:17:57 +02:00
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-12-09 17:03:16 +01:00
if err := xTransport . resolveAndUpdateCache ( host ) ; err != nil {
2019-11-17 22:00:08 +01:00
dlog . Errorf ( "Unable to resolve [%v] - Make sure that the system resolver works, or that `fallback_resolver` has been set to a resolver that can be reached" , host )
2019-10-20 17:09:14 +02:00
return nil , 0 , err
}
2018-01-30 15:51:07 +01:00
req := & http . Request {
Method : method ,
URL : url ,
Header : header ,
Close : false ,
}
if body != nil {
2018-11-22 17:23:22 +01:00
req . ContentLength = int64 ( len ( * body ) )
2019-10-16 12:20:40 +02:00
req . Body = ioutil . NopCloser ( bytes . NewReader ( * body ) )
2018-01-30 15:51:07 +01:00
}
2018-01-30 17:37:35 +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 {
2019-10-16 11:49:18 +02:00
err = errors . New ( resp . Status )
2018-01-30 15:51:07 +01:00
}
2018-02-05 19:03:04 +01:00
} else {
( * xTransport . transport ) . CloseIdleConnections ( )
2018-01-30 15:51:07 +01:00
}
2018-01-30 17:37:35 +01:00
if err != nil {
dlog . Debugf ( "[%s]: [%s]" , req . URL , err )
2018-04-11 11:42:30 +02:00
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 17:37:35 +01:00
}
2018-01-30 15:51:07 +01:00
return resp , rtt , err
}
2018-02-05 11:30:10 +01:00
func ( xTransport * XTransport ) Get ( url * url . URL , accept string , timeout time . Duration ) ( * http . Response , time . Duration , error ) {
2019-12-22 17:34:16 +01:00
return xTransport . Fetch ( "GET" , url , accept , "" , nil , timeout )
2018-01-30 15:51:07 +01:00
}
2019-12-22 17:34:16 +01:00
func ( xTransport * XTransport ) Post ( url * url . URL , accept string , contentType string , body * [ ] byte , timeout time . Duration ) ( * http . Response , time . Duration , error ) {
return xTransport . Fetch ( "POST" , url , accept , contentType , body , timeout )
2018-01-30 15:51:07 +01:00
}
2018-02-27 09:30:09 +01:00
2018-02-05 11:30:10 +01:00
func ( xTransport * XTransport ) DoHQuery ( useGet bool , url * url . URL , body [ ] byte , timeout time . Duration ) ( * http . Response , time . Duration , error ) {
2018-02-27 09:30:09 +01:00
padLen := 63 - ( len ( body ) + 63 ) & 63
2018-05-19 02:39:32 +02:00
dataType := "application/dns-message"
2019-12-22 18:02:33 +01:00
paddedBody := addEDNS0PaddingIfNoneFound ( & body , padLen )
2018-02-05 11:30:10 +01:00
if useGet {
qs := url . Query ( )
qs . Add ( "ct" , "" )
2019-12-22 17:34:16 +01:00
encBody := base64 . RawURLEncoding . EncodeToString ( * paddedBody )
2018-02-05 11:36:15 +01:00
qs . Add ( "dns" , encBody )
2018-02-05 11:30:10 +01:00
url2 := * url
url2 . RawQuery = qs . Encode ( )
return xTransport . Get ( & url2 , dataType , timeout )
}
2019-12-22 17:34:16 +01:00
return xTransport . Post ( url , dataType , dataType , paddedBody , timeout )
2018-02-27 09:30:09 +01:00
}
func ( xTransport * XTransport ) makePad ( padLen int ) * string {
padding := strings . Repeat ( "X" , padLen )
return & padding
2018-02-05 11:30:10 +01:00
}