Initial support for DNS-over-HTTP2 -- Yes, it works with Google.

This commit is contained in:
Frank Denis 2018-01-27 15:26:08 +01:00
parent 85f8aa1000
commit 50d0c0449f
3 changed files with 136 additions and 12 deletions

View File

@ -14,7 +14,7 @@
## List of servers to use
## If this line is commented, all registered servers will be used
# server_names = ['scaleway-fr']
server_names = ['google']
## List of local addresses and ports to listen to. Can be IPv4 and/or IPv6.
@ -258,6 +258,6 @@ format = 'tsv'
## Optional, local, static list of additional servers
## Mostly useful for testing your own servers.
# [static]
# [static.'example.com']
# stamp = 'sdns://AQMAAAAAAAAAEjIxMi40Ny4yMjguMTM2OjQ0MyDoAbhOpga_sLrAzkNEW7FeumSwL6PEqjGuEGNqB5AyTR8yLmRuc2NyeXB0LWNlcnQuZnIuZG5zY3J5cHQub3Jn'
[static]
[static.'google']
stamp = 'sdns://AgAAAAAAAAAADjIxNi41OC4yMDQuMTQyAA5kbnMuZ29vZ2xlLmNvbQ0vZXhwZXJpbWVudGFs'

View File

@ -1,10 +1,13 @@
package main
import (
"bytes"
"crypto/rand"
"flag"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"path/filepath"
"sync"
@ -53,6 +56,7 @@ type Proxy struct {
urlsToPrefetch []URLToPrefetch
clientsCount uint32
maxClients uint32
httpTransport *http.Transport
}
type App struct {
@ -151,6 +155,15 @@ func (proxy *Proxy) StartProxy() {
for _, registeredServer := range proxy.registeredServers {
proxy.serversInfo.registerServer(proxy, registeredServer.name, registeredServer.stamp)
}
proxy.httpTransport = &http.Transport{
DisableKeepAlives: false,
DisableCompression: true,
MaxIdleConns: 1,
IdleConnTimeout: proxy.timeout,
ResponseHeaderTimeout: proxy.timeout,
ExpectContinueTimeout: proxy.timeout,
MaxResponseHeaderBytes: 4096,
}
for _, listenAddrStr := range proxy.listenAddresses {
listenUDPAddr, err := net.ResolveUDPAddr("udp", listenAddrStr)
if err != nil {
@ -353,15 +366,50 @@ func (proxy *Proxy) processIncomingQuery(serverInfo *ServerInfo, clientProto str
}
}
if len(response) == 0 {
encryptedQuery, clientNonce, err := proxy.Encrypt(serverInfo, query, serverProto)
if err != nil {
return
}
serverInfo.noticeBegin(proxy)
if serverProto == "udp" {
response, err = proxy.exchangeWithUDPServer(serverInfo, encryptedQuery, clientNonce)
if serverInfo.Proto == StampProtoTypeDNSCrypt {
encryptedQuery, clientNonce, err := proxy.Encrypt(serverInfo, query, serverProto)
if err != nil {
return
}
serverInfo.noticeBegin(proxy)
if serverProto == "udp" {
response, err = proxy.exchangeWithUDPServer(serverInfo, encryptedQuery, clientNonce)
} else {
response, err = proxy.exchangeWithTCPServer(serverInfo, encryptedQuery, clientNonce)
}
if err != nil {
serverInfo.noticeFailure(proxy)
return
}
} else if serverInfo.Proto == StampProtoTypeDoH {
req := &http.Request{
Method: "POST",
URL: serverInfo.URL,
Host: serverInfo.HostName,
Header: map[string][]string{
"Accept": {"application/dns-udpwireformat"},
"Content-Type": {"application/dns-udpwireformat"},
"User-Agent": {"dnscrypt-proxy"},
},
Close: false,
Body: ioutil.NopCloser(bytes.NewReader(query)),
}
client := http.Client{
Transport: proxy.httpTransport,
Timeout: proxy.timeout,
}
resp, err := client.Do(req)
if err == nil && resp != nil && (resp.StatusCode < 200 || resp.StatusCode > 299) {
return
} else if err != nil || resp == nil {
return
}
response, err = ioutil.ReadAll(resp.Body)
if err != nil {
return
}
} else {
response, err = proxy.exchangeWithTCPServer(serverInfo, encryptedQuery, clientNonce)
dlog.Fatal("Unsupported protocol")
}
if err != nil {
serverInfo.noticeFailure(proxy)
@ -369,6 +417,10 @@ func (proxy *Proxy) processIncomingQuery(serverInfo *ServerInfo, clientProto str
}
response, _ = pluginsState.ApplyResponsePlugins(&proxy.pluginsGlobals, response)
}
if len(response) < MinDNSPacketSize || len(response) > MaxDNSPacketSize {
serverInfo.noticeFailure(proxy)
return
}
if clientProto == "udp" {
if len(response) > MaxDNSUDPPacketSize {
response, err = TruncatedResponse(response)

View File

@ -1,9 +1,15 @@
package main
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"net"
"net/http"
"net/url"
"strings"
"sync"
"time"
@ -32,12 +38,15 @@ type RegisteredServer struct {
type ServerInfo struct {
sync.RWMutex
Proto StampProtoType
MagicQuery [8]byte
ServerPk [32]byte
SharedKey [32]byte
CryptoConstruction CryptoConstruction
Name string
Timeout time.Duration
URL *url.URL
HostName string
UDPAddr *net.UDPAddr
TCPAddr *net.TCPAddr
lastActionTS time.Time
@ -142,6 +151,15 @@ func (serversInfo *ServersInfo) getOne() *ServerInfo {
}
func (serversInfo *ServersInfo) fetchServerInfo(proxy *Proxy, name string, stamp ServerStamp) (ServerInfo, error) {
if stamp.proto == StampProtoTypeDNSCrypt {
return serversInfo.fetchDNSCryptServerInfo(proxy, name, stamp)
} else if stamp.proto == StampProtoTypeDoH {
return serversInfo.fetchDoHServerInfo(proxy, name, stamp)
}
return ServerInfo{}, errors.New("Unsupported protocol")
}
func (serversInfo *ServersInfo) fetchDNSCryptServerInfo(proxy *Proxy, name string, stamp ServerStamp) (ServerInfo, error) {
if len(stamp.serverPk) != ed25519.PublicKeySize {
serverPk, err := hex.DecodeString(strings.Replace(string(stamp.serverPk), ":", "", -1))
if err != nil || len(serverPk) != ed25519.PublicKeySize {
@ -163,6 +181,7 @@ func (serversInfo *ServersInfo) fetchServerInfo(proxy *Proxy, name string, stamp
return ServerInfo{}, err
}
serverInfo := ServerInfo{
Proto: StampProtoTypeDNSCrypt,
MagicQuery: certInfo.MagicQuery,
ServerPk: certInfo.ServerPk,
SharedKey: certInfo.SharedKey,
@ -176,6 +195,59 @@ func (serversInfo *ServersInfo) fetchServerInfo(proxy *Proxy, name string, stamp
return serverInfo, nil
}
func (serversInfo *ServersInfo) fetchDoHServerInfo(proxy *Proxy, name string, stamp ServerStamp) (ServerInfo, error) {
url := &url.URL{
Scheme: "https",
Host: stamp.providerName,
Path: stamp.path,
}
body := ioutil.NopCloser(bytes.NewReader([]byte{
0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01,
}))
req := &http.Request{
Method: "POST",
URL: url,
Header: map[string][]string{
"Accept": {"application/dns-udpwireformat"},
"Content-Type": {"application/dns-udpwireformat"},
"User-Agent": {"dnscrypt-proxy"},
},
Close: false,
Host: stamp.providerName,
Body: body,
}
client := http.Client{
Transport: proxy.httpTransport,
Timeout: proxy.timeout,
}
start := time.Now()
resp, err := client.Do(req)
elapsed := time.Since(start)
if err == nil && resp != nil && (resp.StatusCode < 200 || resp.StatusCode > 299) {
return ServerInfo{}, fmt.Errorf("Webserver returned code %d", resp.StatusCode)
} else if err != nil {
return ServerInfo{}, err
} else if resp == nil {
return ServerInfo{}, errors.New("Webserver returned an error")
}
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return ServerInfo{}, err
}
if len(respBody) < MinDNSPacketSize || len(respBody) > MaxDNSPacketSize {
return ServerInfo{}, errors.New("Webserver returned an unexpected response")
}
serverInfo := ServerInfo{
Proto: StampProtoTypeDoH,
Name: name,
Timeout: proxy.timeout,
URL: url,
HostName: stamp.providerName,
initialRtt: int(elapsed.Nanoseconds() / 1000000),
}
return serverInfo, nil
}
func (serverInfo *ServerInfo) noticeFailure(proxy *Proxy) {
serverInfo.Lock()
serverInfo.rtt.Set(float64(proxy.timeout.Nanoseconds()))