2018-11-28 12:40:56 +00:00
package dnsforward
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"math/rand"
2018-11-28 15:40:20 +00:00
"net"
2018-11-28 12:40:56 +00:00
"net/http"
"net/url"
"strings"
"sync"
"time"
2018-12-17 22:20:38 +00:00
"github.com/jedisct1/go-dnsstamps"
"github.com/ameshkov/dnscrypt"
2018-11-28 12:40:56 +00:00
"github.com/joomcode/errorx"
"github.com/miekg/dns"
)
const defaultTimeout = time . Second * 10
type Upstream interface {
Exchange ( m * dns . Msg ) ( * dns . Msg , error )
2018-12-05 09:57:14 +00:00
Address ( ) string
2018-11-28 12:40:56 +00:00
}
//
// plain DNS
//
type plainDNS struct {
2018-12-05 21:22:20 +00:00
boot bootstrapper
2018-12-05 18:33:32 +00:00
preferTCP bool
2018-11-28 12:40:56 +00:00
}
var defaultUDPClient = dns . Client {
Timeout : defaultTimeout ,
UDPSize : dns . MaxMsgSize ,
}
var defaultTCPClient = dns . Client {
Net : "tcp" ,
UDPSize : dns . MaxMsgSize ,
Timeout : defaultTimeout ,
}
2018-12-05 21:22:20 +00:00
// Address returns the original address that we've put in initially, not resolved one
func ( p * plainDNS ) Address ( ) string { return p . boot . address }
2018-12-05 09:57:14 +00:00
2018-11-28 12:40:56 +00:00
func ( p * plainDNS ) Exchange ( m * dns . Msg ) ( * dns . Msg , error ) {
2018-12-05 21:22:20 +00:00
addr , _ , err := p . boot . get ( )
if err != nil {
return nil , err
}
2018-12-05 18:33:32 +00:00
if p . preferTCP {
2018-12-05 21:22:20 +00:00
reply , _ , err := defaultTCPClient . Exchange ( m , addr )
2018-12-05 18:33:32 +00:00
return reply , err
}
2018-12-05 21:22:20 +00:00
reply , _ , err := defaultUDPClient . Exchange ( m , addr )
2018-11-28 12:40:56 +00:00
if err != nil && reply != nil && reply . Truncated {
log . Printf ( "Truncated message was received, retrying over TCP, question: %s" , m . Question [ 0 ] . String ( ) )
2018-12-05 21:22:20 +00:00
reply , _ , err = defaultTCPClient . Exchange ( m , addr )
2018-11-28 12:40:56 +00:00
}
2018-12-05 21:22:20 +00:00
2018-11-28 12:40:56 +00:00
return reply , err
}
//
// DNS-over-TLS
//
type dnsOverTLS struct {
2018-12-05 21:22:20 +00:00
boot bootstrapper
pool * TLSPool
2018-11-28 12:40:56 +00:00
sync . RWMutex // protects pool
}
2018-12-05 21:22:20 +00:00
func ( p * dnsOverTLS ) Address ( ) string { return p . boot . address }
2018-12-05 09:57:14 +00:00
2018-11-28 12:40:56 +00:00
func ( p * dnsOverTLS ) Exchange ( m * dns . Msg ) ( * dns . Msg , error ) {
var pool * TLSPool
p . RLock ( )
pool = p . pool
p . RUnlock ( )
if pool == nil {
p . Lock ( )
// lazy initialize it
2018-12-05 21:22:20 +00:00
p . pool = & TLSPool { boot : & p . boot }
2018-11-28 12:40:56 +00:00
p . Unlock ( )
}
p . RLock ( )
poolConn , err := p . pool . Get ( )
p . RUnlock ( )
if err != nil {
2018-12-05 21:22:20 +00:00
return nil , errorx . Decorate ( err , "Failed to get a connection from TLSPool to %s" , p . Address ( ) )
2018-11-28 12:40:56 +00:00
}
c := dns . Conn { Conn : poolConn }
err = c . WriteMsg ( m )
if err != nil {
poolConn . Close ( )
2018-12-05 21:22:20 +00:00
return nil , errorx . Decorate ( err , "Failed to send a request to %s" , p . Address ( ) )
2018-11-28 12:40:56 +00:00
}
reply , err := c . ReadMsg ( )
if err != nil {
poolConn . Close ( )
2018-12-05 21:22:20 +00:00
return nil , errorx . Decorate ( err , "Failed to read a request from %s" , p . Address ( ) )
2018-11-28 12:40:56 +00:00
}
p . RLock ( )
p . pool . Put ( poolConn )
p . RUnlock ( )
return reply , nil
}
//
// DNS-over-https
//
type dnsOverHTTPS struct {
2018-12-05 21:22:20 +00:00
boot bootstrapper
2018-11-28 12:40:56 +00:00
}
2018-12-05 21:22:20 +00:00
func ( p * dnsOverHTTPS ) Address ( ) string { return p . boot . address }
2018-12-05 09:57:14 +00:00
2018-11-28 12:40:56 +00:00
func ( p * dnsOverHTTPS ) Exchange ( m * dns . Msg ) ( * dns . Msg , error ) {
2018-12-05 21:22:20 +00:00
addr , tlsConfig , err := p . boot . get ( )
if err != nil {
return nil , errorx . Decorate ( err , "Couldn't bootstrap %s" , p . boot . address )
}
2018-11-28 12:40:56 +00:00
buf , err := m . Pack ( )
if err != nil {
return nil , errorx . Decorate ( err , "Couldn't pack request msg" )
}
bb := bytes . NewBuffer ( buf )
2018-12-05 21:22:20 +00:00
// set up a custom request with custom URL
url , err := url . Parse ( p . boot . address )
if err != nil {
return nil , errorx . Decorate ( err , "Couldn't parse URL %s" , p . boot . address )
}
req := http . Request {
Method : "POST" ,
URL : url ,
Body : ioutil . NopCloser ( bb ) ,
Header : make ( http . Header ) ,
Host : url . Host ,
}
url . Host = addr
req . Header . Set ( "Content-Type" , "application/dns-message" )
client := http . Client {
Transport : & http . Transport { TLSClientConfig : tlsConfig } ,
}
resp , err := client . Do ( & req )
2018-11-28 12:40:56 +00:00
if resp != nil && resp . Body != nil {
defer resp . Body . Close ( )
}
if err != nil {
2018-12-05 21:22:20 +00:00
return nil , errorx . Decorate ( err , "Couldn't do a POST request to '%s'" , addr )
2018-11-28 12:40:56 +00:00
}
2018-12-05 21:22:20 +00:00
2018-11-28 12:40:56 +00:00
body , err := ioutil . ReadAll ( resp . Body )
if err != nil {
2018-12-05 21:22:20 +00:00
return nil , errorx . Decorate ( err , "Couldn't read body contents for '%s'" , addr )
2018-11-28 12:40:56 +00:00
}
if resp . StatusCode != http . StatusOK {
2018-12-05 21:22:20 +00:00
return nil , fmt . Errorf ( "Got an unexpected HTTP status code %d from '%s'" , resp . StatusCode , addr )
2018-11-28 12:40:56 +00:00
}
if len ( body ) == 0 {
2018-12-05 21:22:20 +00:00
return nil , fmt . Errorf ( "Got an unexpected empty body from '%s'" , addr )
2018-11-28 12:40:56 +00:00
}
response := dns . Msg { }
err = response . Unpack ( body )
if err != nil {
2018-12-05 21:22:20 +00:00
return nil , errorx . Decorate ( err , "Couldn't unpack DNS response from '%s': body is %s" , addr , string ( body ) )
2018-11-28 12:40:56 +00:00
}
return & response , nil
}
2018-12-17 22:20:38 +00:00
//
// DNSCrypt
//
type dnsCrypt struct {
boot bootstrapper
client * dnscrypt . Client // DNSCrypt client properties
serverInfo * dnscrypt . ServerInfo // DNSCrypt server info
sync . RWMutex // protects DNSCrypt client
}
func ( p * dnsCrypt ) Address ( ) string { return p . boot . address }
func ( p * dnsCrypt ) Exchange ( m * dns . Msg ) ( * dns . Msg , error ) {
var client * dnscrypt . Client
var serverInfo * dnscrypt . ServerInfo
p . RLock ( )
client = p . client
serverInfo = p . serverInfo
p . RUnlock ( )
2018-12-17 22:45:19 +00:00
now := uint32 ( time . Now ( ) . Unix ( ) )
if client == nil || serverInfo == nil || ( serverInfo != nil && serverInfo . ServerCert . NotAfter < now ) {
2018-12-17 22:20:38 +00:00
p . Lock ( )
// Using "udp" for DNSCrypt upstreams by default
2018-12-18 10:24:15 +00:00
client = & dnscrypt . Client { Timeout : defaultTimeout , AdjustPayloadSize : true }
2018-12-17 22:20:38 +00:00
si , _ , err := client . Dial ( p . boot . address )
if err != nil {
p . Unlock ( )
return nil , errorx . Decorate ( err , "Failed to fetch certificate info from %s" , p . Address ( ) )
}
p . client = client
p . serverInfo = si
serverInfo = si
p . Unlock ( )
}
reply , _ , err := client . Exchange ( m , serverInfo )
2018-12-17 22:45:19 +00:00
if err , ok := err . ( net . Error ) ; ok && err . Timeout ( ) {
// If request times out, it is possible that the server configuration has been changed.
// It is safe to assume that the key was rotated (for instance, as it is described here: https://dnscrypt.pl/2017/02/26/how-key-rotation-is-automated/).
// We should re-fetch the server certificate info so that the new requests were not failing.
p . Lock ( )
p . client = nil
p . serverInfo = nil
p . Unlock ( )
}
2018-12-17 22:20:38 +00:00
return reply , err
}
2018-11-28 12:40:56 +00:00
func ( s * Server ) chooseUpstream ( ) Upstream {
upstreams := s . Upstreams
if upstreams == nil {
upstreams = defaultValues . Upstreams
}
if len ( upstreams ) == 0 {
panic ( "SHOULD NOT HAPPEN: no default upstreams specified" )
}
if len ( upstreams ) == 1 {
return upstreams [ 0 ]
}
n := rand . Intn ( len ( upstreams ) )
upstream := upstreams [ n ]
return upstream
}
2018-12-05 21:22:20 +00:00
func AddressToUpstream ( address string , bootstrap string ) ( Upstream , error ) {
2018-11-28 12:40:56 +00:00
if strings . Contains ( address , "://" ) {
url , err := url . Parse ( address )
if err != nil {
return nil , errorx . Decorate ( err , "Failed to parse %s" , address )
}
switch url . Scheme {
2018-12-17 22:20:38 +00:00
case "sdns" :
stamp , err := dnsstamps . NewServerStampFromString ( address )
if err != nil {
return nil , errorx . Decorate ( err , "Failed to parse %s" , address )
}
switch stamp . Proto {
case dnsstamps . StampProtoTypeDNSCrypt :
return & dnsCrypt { boot : toBoot ( url . String ( ) , bootstrap ) } , nil
case dnsstamps . StampProtoTypeDoH :
return AddressToUpstream ( fmt . Sprintf ( "https://%s%s" , stamp . ProviderName , stamp . Path ) , bootstrap )
}
return nil , fmt . Errorf ( "Unsupported protocol %v in %s" , stamp . Proto , address )
2018-11-28 12:40:56 +00:00
case "dns" :
2018-11-28 15:40:20 +00:00
if url . Port ( ) == "" {
url . Host += ":53"
}
2018-12-05 21:22:20 +00:00
return & plainDNS { boot : toBoot ( url . Host , bootstrap ) } , nil
2018-12-05 18:33:32 +00:00
case "tcp" :
if url . Port ( ) == "" {
url . Host += ":53"
}
2018-12-05 21:22:20 +00:00
return & plainDNS { boot : toBoot ( url . Host , bootstrap ) , preferTCP : true } , nil
2018-11-28 12:40:56 +00:00
case "tls" :
2018-11-28 15:40:20 +00:00
if url . Port ( ) == "" {
url . Host += ":853"
}
2018-12-05 21:22:20 +00:00
return & dnsOverTLS { boot : toBoot ( url . String ( ) , bootstrap ) } , nil
2018-11-28 12:40:56 +00:00
case "https" :
2018-12-05 21:22:20 +00:00
if url . Port ( ) == "" {
url . Host += ":443"
}
return & dnsOverHTTPS { boot : toBoot ( url . String ( ) , bootstrap ) } , nil
2018-11-28 12:40:56 +00:00
default :
2018-11-28 15:40:20 +00:00
// assume it's plain DNS
if url . Port ( ) == "" {
url . Host += ":53"
}
2018-12-05 21:22:20 +00:00
return & plainDNS { boot : toBoot ( url . String ( ) , bootstrap ) } , nil
2018-11-28 12:40:56 +00:00
}
}
// we don't have scheme in the url, so it's just a plain DNS host:port
2018-11-28 15:40:20 +00:00
_ , _ , err := net . SplitHostPort ( address )
if err != nil {
// doesn't have port, default to 53
address = net . JoinHostPort ( address , "53" )
}
2018-12-05 21:22:20 +00:00
return & plainDNS { boot : toBoot ( address , bootstrap ) } , nil
2018-11-28 12:40:56 +00:00
}