1
0
Fork 0
This commit is contained in:
Andrey Meshkov 2018-12-17 01:55:58 +03:00
commit 63ea7215f7
9 changed files with 786 additions and 0 deletions

8
.codecov.yml Normal file
View File

@ -0,0 +1,8 @@
coverage:
status:
project:
default:
target: 40%
threshold: null
patch: false
changes: false

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.idea
.vscode

12
.travis.yml Normal file
View File

@ -0,0 +1,12 @@
language: go
sudo: false
go:
- 1.11.x
- 1.x
script:
- go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
after_success:
- bash <(curl -s https://codecov.io/bash)

25
LICENSE Normal file
View File

@ -0,0 +1,25 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>

33
README.md Normal file
View File

@ -0,0 +1,33 @@
[![Build Status](https://travis-ci.org/ameshkov/dnscrypt.svg?branch=master)](https://travis-ci.org/ameshkov/dnscrypt)
[![Code Coverage](https://img.shields.io/codecov/c/github/ameshkov/dnscrypt/master.svg)](https://codecov.io/github/ameshkov/dnscrypt?branch=master)
# DNSCrypt Client
This is a very simple [DNSCrypt](https://dnscrypt.info/) client library written in Go.
The idea is to let others use DNSCrypt resolvers in the same manner as we can use regular and DoT resolvers with [miekg's DNS library](https://github.com/miekg/dns).
Unfortunately, I have not found an easy way to use [dnscrypt-proxy](https://github.com/jedisct1/dnscrypt-proxy) as a dependency so here's why this library was created.
### Usage
```
// AdGuard DNS stamp
stampStr := "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20"
// Initializing the DNSCrypt client
c := dnscrypt.Client{Proto: "udp", Timeout: 10 * time.Second}
// Fetching and validating the server certificate
serverInfo, rtt, err := client.Dial(stampStr)
// Create a DNS request
req := dns.Msg{}
req.Id = dns.Id()
req.RecursionDesired = true
req.Question = []dns.Question{
{Name: "google-public-dns-a.google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
}
// Get the DNS response
reply, rtt, err := client.Exchange(&req, serverInfo)
```

547
dnscrypt.go Normal file
View File

@ -0,0 +1,547 @@
package dnscrypt
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"log"
"math/rand"
"net"
"strings"
"time"
"github.com/jedisct1/go-dnsstamps"
"github.com/jedisct1/xsecretbox"
"github.com/miekg/dns"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/nacl/box"
"golang.org/x/crypto/nacl/secretbox"
)
type CryptoConstruction uint16
const (
UndefinedConstruction CryptoConstruction = iota
XSalsa20Poly1305
XChacha20Poly1305
)
var (
CertMagic = [4]byte{0x44, 0x4e, 0x53, 0x43}
ServerMagic = [8]byte{0x72, 0x36, 0x66, 0x6e, 0x76, 0x57, 0x6a, 0x38}
MinDNSPacketSize = 12 + 5
MaxDNSPacketSize = 4096
MaxDNSUDPPacketSize = 1252
)
const (
ClientMagicLen = 8
)
const (
NonceSize = xsecretbox.NonceSize
HalfNonceSize = xsecretbox.NonceSize / 2
TagSize = xsecretbox.TagSize
PublicKeySize = 32
QueryOverhead = ClientMagicLen + PublicKeySize + HalfNonceSize + TagSize
ResponseOverhead = len(ServerMagic) + NonceSize + TagSize
// Some servers do not work if padded length is less than 256. Example: Quad9
MinQuestionSize = 256
)
type Client struct {
Proto string // Protocol ("udp" or "tcp")
Timeout time.Duration // Timeout for read/write operations
}
// DnsCrypt server certificate data
type CertInfo struct {
Serial uint32
ServerPk [32]byte
SharedKey [32]byte
MagicQuery [ClientMagicLen]byte
CryptoConstruction CryptoConstruction
ForwardSecurity bool
}
// DNSCrypt server information necessary for decryption/encryption
type ServerInfo struct {
SecretKey [32]byte // Client secret key
PublicKey [32]byte // Client public key
Proto string // Protocol ("udp" or "tcp")
ServerPublicKey ed25519.PublicKey // Server public key
ServerAddress string // Server IP address
ProviderName string // Provider name
ServerCert *CertInfo // Certificate info (obtained with the first unencrypted DNS request)
}
// Fetches and validates DNSCrypt certificate from the given server
// Data received during this call is then used for DNS requests encryption/decryption
// stampStr is an sdns:// address which is parsed using go-dnsstamps package
func (c *Client) Dial(stampStr string) (*ServerInfo, time.Duration, error) {
stamp, err := dnsstamps.NewServerStampFromString(stampStr)
if err != nil {
// Invalid SDNS stamp
return nil, 0, err
}
if stamp.Proto != dnsstamps.StampProtoTypeDNSCrypt {
return nil, 0, errors.New("stamp is not for a DNSCrypt server")
}
return c.DialStamp(stamp)
}
// Fetches and validates DNSCrypt certificate from the given server
// Data received during this call is then used for DNS requests encryption/decryption
func (c *Client) DialStamp(stamp dnsstamps.ServerStamp) (*ServerInfo, time.Duration, error) {
serverInfo := ServerInfo{}
// Generate the secret/public pair
if _, err := rand.Read(serverInfo.SecretKey[:]); err != nil {
return nil, 0, err
}
curve25519.ScalarBaseMult(&serverInfo.PublicKey, &serverInfo.SecretKey)
// Set the provider properties
serverInfo.Proto = c.Proto
serverInfo.ServerPublicKey = stamp.ServerPk
serverInfo.ServerAddress = stamp.ServerAddrStr
serverInfo.ProviderName = stamp.ProviderName
if !strings.HasSuffix(serverInfo.ProviderName, ".") {
serverInfo.ProviderName = serverInfo.ProviderName + "."
}
// Fetch the certificate and validate it
certInfo, rtt, err := serverInfo.fetchCurrentDNSCryptCert(c.Timeout)
if err != nil {
return nil, rtt, err
}
serverInfo.ServerCert = &certInfo
return &serverInfo, rtt, nil
}
// Performs a synchronous DNS query to the specified DNSCrypt server and returns a DNS response.
// This method creates a new network connection for every call so avoid using it for TCP.
// DNSCrypt server information needs to be fetched and validated prior to this call using the c.DialStamp method.
func (c *Client) Exchange(m *dns.Msg, s *ServerInfo) (*dns.Msg, time.Duration, error) {
now := time.Now()
conn, err := net.Dial(c.Proto, s.ServerAddress)
if err != nil {
return nil, 0, err
}
defer conn.Close()
r, _, err := c.ExchangeConn(m, s, conn)
if err != nil {
return nil, 0, err
}
rtt := time.Since(now)
return r, rtt, nil
}
// Performs a synchronous DNS query to the specified DNSCrypt server and returns a DNS response.
// DNSCrypt server information needs to be fetched and validated prior to this call using the c.DialStamp method
func (c *Client) ExchangeConn(m *dns.Msg, s *ServerInfo, conn net.Conn) (*dns.Msg, time.Duration, error) {
now := time.Now()
c.adjustPayloadSize(m)
query, err := m.Pack()
if err != nil {
return nil, 0, err
}
encryptedQuery, clientNonce, err := s.encrypt(query)
if err != nil {
return nil, 0, err
}
if c.Proto == "tcp" {
encryptedQuery, err = prefixWithSize(encryptedQuery)
if err != nil {
return nil, 0, err
}
}
conn.SetDeadline(time.Now().Add(c.Timeout))
conn.Write(encryptedQuery)
encryptedResponse := make([]byte, MaxDNSPacketSize)
if c.Proto == "tcp" {
encryptedResponse, err = readPrefixed(conn)
if err != nil {
return nil, 0, err
}
} else {
length, err := conn.Read(encryptedResponse)
if err != nil {
return nil, 0, err
}
encryptedResponse = encryptedResponse[:length]
}
decrypted, err := s.decrypt(encryptedResponse, clientNonce)
if err != nil {
// TODO: we should somehow distinguish this case as we might need to re-dial for the server certificate when it happens
return nil, 0, err
}
r := dns.Msg{}
err = r.Unpack(decrypted)
if err != nil {
return nil, 0, err
}
rtt := time.Since(now)
return &r, rtt, nil
}
// Adjusts the maximum payload size advertised in queries sent to upstream servers
// See https://github.com/jedisct1/dnscrypt-proxy/blob/master/dnscrypt-proxy/plugin_get_set_payload_size.go
// TODO: I don't really understand why it is required :)
func (c *Client) adjustPayloadSize(msg *dns.Msg) {
originalMaxPayloadSize := 512 - ResponseOverhead
edns0 := msg.IsEdns0()
dnssec := false
if edns0 != nil {
originalMaxPayloadSize = min(int(edns0.UDPSize())-ResponseOverhead, originalMaxPayloadSize)
dnssec = edns0.Do()
}
var options *[]dns.EDNS0
maxPayloadSize := MaxDNSUDPPacketSize - ResponseOverhead
maxPayloadSize = min(MaxDNSUDPPacketSize-ResponseOverhead, max(originalMaxPayloadSize, maxPayloadSize))
if maxPayloadSize > 512 {
var extra2 []dns.RR
for _, extra := range msg.Extra {
if extra.Header().Rrtype != dns.TypeOPT {
extra2 = append(extra2, extra)
} else if xoptions := &extra.(*dns.OPT).Option; len(*xoptions) > 0 && options == nil {
options = xoptions
}
}
msg.Extra = extra2
msg.SetEdns0(uint16(maxPayloadSize), dnssec)
if options != nil {
for _, extra := range msg.Extra {
if extra.Header().Rrtype == dns.TypeOPT {
extra.(*dns.OPT).Option = *options
break
}
}
}
}
}
func (s *ServerInfo) fetchCurrentDNSCryptCert(timeout time.Duration) (CertInfo, time.Duration, error) {
if len(s.ServerPublicKey) != ed25519.PublicKeySize {
return CertInfo{}, 0, errors.New("invalid public key length")
}
query := new(dns.Msg)
query.SetQuestion(s.ProviderName, dns.TypeTXT)
client := dns.Client{Net: s.Proto, UDPSize: uint16(MaxDNSUDPPacketSize), Timeout: timeout}
in, rtt, err := client.Exchange(query, s.ServerAddress)
if err != nil {
return CertInfo{}, 0, err
}
certInfo := CertInfo{CryptoConstruction: UndefinedConstruction}
for _, answerRr := range in.Answer {
recCertInfo, err := txtToCertInfo(answerRr, s)
if err != nil {
log.Printf("[%v] %s", s.ProviderName, err)
continue
}
if recCertInfo.Serial < certInfo.Serial {
log.Printf("[%v] Superseded by a previous certificate", s.ProviderName)
continue
}
if recCertInfo.Serial == certInfo.Serial {
if recCertInfo.CryptoConstruction > certInfo.CryptoConstruction {
log.Printf("[%v] Upgrading the construction from %v to %v", s.ProviderName, certInfo.CryptoConstruction, recCertInfo.CryptoConstruction)
} else {
log.Printf("[%v] Keeping the previous, preferred crypto construction", s.ProviderName)
continue
}
}
// Set the cert info
certInfo = recCertInfo
}
if certInfo.CryptoConstruction == UndefinedConstruction {
return certInfo, 0, errors.New("no useable certificate found")
}
return certInfo, rtt, nil
}
func (s *ServerInfo) encrypt(packet []byte) (encrypted []byte, clientNonce []byte, err error) {
nonce, clientNonce := make([]byte, NonceSize), make([]byte, HalfNonceSize)
rand.Read(clientNonce)
copy(nonce, clientNonce)
var publicKey *[PublicKeySize]byte
sharedKey := &s.ServerCert.SharedKey
publicKey = &s.PublicKey
minQuestionSize := QueryOverhead + len(packet)
if s.Proto == "udp" {
minQuestionSize = max(MinQuestionSize, minQuestionSize)
} else {
var xpad [1]byte
rand.Read(xpad[:])
minQuestionSize += int(xpad[0])
}
paddedLength := min(MaxDNSUDPPacketSize, (max(minQuestionSize, QueryOverhead)+63) & ^63)
if QueryOverhead+len(packet)+1 > paddedLength {
err = errors.New("question too large; cannot be padded")
return
}
encrypted = append(s.ServerCert.MagicQuery[:], publicKey[:]...)
encrypted = append(encrypted, nonce[:HalfNonceSize]...)
padded := pad(packet, paddedLength-QueryOverhead)
if s.ServerCert.CryptoConstruction == XChacha20Poly1305 {
encrypted = xsecretbox.Seal(encrypted, nonce, padded, sharedKey[:])
} else {
var xsalsaNonce [24]byte
copy(xsalsaNonce[:], nonce)
encrypted = secretbox.Seal(encrypted, padded, &xsalsaNonce, sharedKey)
}
return
}
func (s *ServerInfo) decrypt(encrypted []byte, nonce []byte) ([]byte, error) {
sharedKey := &s.ServerCert.SharedKey
serverMagicLen := len(ServerMagic)
responseHeaderLen := serverMagicLen + NonceSize
if len(encrypted) < responseHeaderLen+TagSize+int(MinDNSPacketSize) ||
len(encrypted) > responseHeaderLen+TagSize+int(MaxDNSPacketSize) ||
!bytes.Equal(encrypted[:serverMagicLen], ServerMagic[:]) {
return encrypted, errors.New("invalid message size or prefix")
}
serverNonce := encrypted[serverMagicLen:responseHeaderLen]
if !bytes.Equal(nonce[:HalfNonceSize], serverNonce[:HalfNonceSize]) {
return encrypted, errors.New("unexpected nonce")
}
var packet []byte
var err error
if s.ServerCert.CryptoConstruction == XChacha20Poly1305 {
packet, err = xsecretbox.Open(nil, serverNonce, encrypted[responseHeaderLen:], sharedKey[:])
} else {
var xsalsaServerNonce [24]byte
copy(xsalsaServerNonce[:], serverNonce)
var ok bool
packet, ok = secretbox.Open(nil, encrypted[responseHeaderLen:], &xsalsaServerNonce, sharedKey)
if !ok {
err = errors.New("incorrect tag")
}
}
if err != nil {
return encrypted, err
}
packet, err = unpad(packet)
if err != nil || len(packet) < MinDNSPacketSize {
return encrypted, errors.New("incorrect padding")
}
return packet, nil
}
func txtToCertInfo(answerRr dns.RR, serverInfo *ServerInfo) (CertInfo, error) {
now := uint32(time.Now().Unix())
certInfo := CertInfo{CryptoConstruction: UndefinedConstruction}
binCert, err := packTxtString(strings.Join(answerRr.(*dns.TXT).Txt, ""))
// Validate the cert basic params
if err != nil {
return certInfo, errors.New("unable to unpack the certificate")
}
if len(binCert) < 124 {
return certInfo, errors.New("certificate is too short")
}
if !bytes.Equal(binCert[:4], CertMagic[:4]) {
return certInfo, errors.New("invalid cert magic")
}
switch esVersion := binary.BigEndian.Uint16(binCert[4:6]); esVersion {
case 0x0001:
certInfo.CryptoConstruction = XSalsa20Poly1305
case 0x0002:
certInfo.CryptoConstruction = XChacha20Poly1305
default:
return certInfo, errors.New(fmt.Sprintf("unsupported crypto construction: %v", esVersion))
}
// Verify the server public key
signature := binCert[8:72]
signed := binCert[72:]
if !ed25519.Verify(serverInfo.ServerPublicKey, signed, signature) {
return certInfo, errors.New("incorrect signature")
}
certInfo.Serial = binary.BigEndian.Uint32(binCert[112:116])
// Validate the certificate date
tsBegin := binary.BigEndian.Uint32(binCert[116:120])
tsEnd := binary.BigEndian.Uint32(binCert[120:124])
if tsBegin >= tsEnd {
return certInfo, errors.New(fmt.Sprintf("certificate ends before it starts (%v >= %v)", tsBegin, tsEnd))
}
if now > tsEnd || now < tsBegin {
return certInfo, errors.New("certificate not valid at the current date")
}
ttl := tsEnd - tsBegin
if ttl > 86400*7 {
certInfo.ForwardSecurity = false
} else {
certInfo.ForwardSecurity = true
}
var serverPk [32]byte
copy(serverPk[:], binCert[72:104])
certInfo.SharedKey = computeSharedKey(certInfo.CryptoConstruction, &serverInfo.SecretKey, &serverPk, &serverInfo.ProviderName)
copy(certInfo.ServerPk[:], serverPk[:])
copy(certInfo.MagicQuery[:], binCert[104:112])
return certInfo, nil
}
func computeSharedKey(cryptoConstruction CryptoConstruction, secretKey *[32]byte, serverPk *[32]byte, providerName *string) (sharedKey [32]byte) {
if cryptoConstruction == XChacha20Poly1305 {
var err error
sharedKey, err = xsecretbox.SharedKey(*secretKey, *serverPk)
if err != nil {
log.Printf("[%v] Weak public key", providerName)
}
} else {
box.Precompute(&sharedKey, serverPk, secretKey)
}
return
}
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, error) {
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, nil
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func pad(packet []byte, minSize int) []byte {
packet = append(packet, 0x80)
for len(packet) < minSize {
packet = append(packet, 0)
}
return packet
}
func unpad(packet []byte) ([]byte, error) {
for i := len(packet); ; {
if i == 0 {
return nil, errors.New("invalid padding (short packet)")
}
i--
if packet[i] == 0x80 {
return packet[:i], nil
} else if packet[i] != 0x00 {
return nil, errors.New("invalid padding (delimiter not found)")
}
}
}
func prefixWithSize(packet []byte) ([]byte, error) {
packetLen := len(packet)
if packetLen > 0xffff {
return packet, errors.New("packet too large")
}
packet = append(append(packet, 0), 0)
copy(packet[2:], packet[:len(packet)-2])
binary.BigEndian.PutUint16(packet[0:2], uint16(len(packet)-2))
return packet, nil
}
func readPrefixed(conn net.Conn) ([]byte, error) {
buf := make([]byte, 2+MaxDNSPacketSize)
packetLength, pos := -1, 0
for {
readnb, err := conn.Read(buf[pos:])
if err != nil {
return buf, err
}
pos += readnb
if pos >= 2 && packetLength < 0 {
packetLength = int(binary.BigEndian.Uint16(buf[0:2]))
if packetLength > MaxDNSPacketSize-1 {
return buf, errors.New("packet too large")
}
if packetLength < MinDNSPacketSize {
return buf, errors.New("packet too short")
}
}
if packetLength >= 0 && pos >= 2+packetLength {
return buf[2 : 2+packetLength], nil
}
}
}

128
dnscrypt_test.go Normal file
View File

@ -0,0 +1,128 @@
package dnscrypt
import (
"log"
"net"
"testing"
"time"
"github.com/jedisct1/go-dnsstamps"
"github.com/miekg/dns"
)
func TestParseStamp(t *testing.T) {
// Google DoH
stampStr := "sdns://AgUAAAAAAAAAAAAOZG5zLmdvb2dsZS5jb20NL2V4cGVyaW1lbnRhbA"
stamp, err := dnsstamps.NewServerStampFromString(stampStr)
if err != nil || stamp.ProviderName == "" {
t.Fatalf("Could not parse stamp %s: %s", stampStr, err)
}
log.Println(stampStr)
log.Printf("Proto=%s\n", stamp.Proto.String())
log.Printf("ProviderName=%s\n", stamp.ProviderName)
log.Printf("Path=%s\n", stamp.Path)
log.Println("")
// AdGuard DNSCrypt
stampStr = "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20"
stamp, err = dnsstamps.NewServerStampFromString(stampStr)
if err != nil || stamp.ProviderName == "" {
t.Fatalf("Could not parse stamp %s: %s", stampStr, err)
}
log.Println(stampStr)
log.Printf("Proto=%s\n", stamp.Proto.String())
log.Printf("ProviderName=%s\n", stamp.ProviderName)
log.Printf("Path=%s\n", stamp.Path)
log.Printf("ServerAddrStr=%s\n", stamp.ServerAddrStr)
log.Println("")
}
func TestDnsCryptResolver(t *testing.T) {
stamps := []struct {
stampStr string
udp bool
}{
{
// AdGuard DNS
stampStr: "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
udp: true,
},
{
// AdGuard DNS Family
stampStr: "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMjo1NDQzILgxXdexS27jIKRw3C7Wsao5jMnlhvhdRUXWuMm1AFq6ITIuZG5zY3J5cHQuZmFtaWx5Lm5zMS5hZGd1YXJkLmNvbQ",
udp: true,
},
{
// Cisco OpenDNS
stampStr: "sdns://AQAAAAAAAAAADjIwOC42Ny4yMjAuMjIwILc1EUAgbyJdPivYItf9aR6hwzzI1maNDL4Ev6vKQ_t5GzIuZG5zY3J5cHQtY2VydC5vcGVuZG5zLmNvbQ",
udp: true,
},
{
// Cisco OpenDNS Family Shield
stampStr: "sdns://AQAAAAAAAAAADjIwOC42Ny4yMjAuMTIzILc1EUAgbyJdPivYItf9aR6hwzzI1maNDL4Ev6vKQ_t5GzIuZG5zY3J5cHQtY2VydC5vcGVuZG5zLmNvbQ",
udp: true,
},
{
// Quad9 (anycast) dnssec/no-log/filter 9.9.9.9
stampStr: "sdns://AQMAAAAAAAAADDkuOS45Ljk6ODQ0MyBnyEe4yHWM0SAkVUO-dWdG3zTfHYTAC4xHA2jfgh2GPhkyLmRuc2NyeXB0LWNlcnQucXVhZDkubmV0",
udp: true,
},
{
// https://securedns.eu/
stampStr: "sdns://AQcAAAAAAAAAEzE0Ni4xODUuMTY3LjQzOjUzNTMgs6WXaRRXWwSJ4Z-unEPmefryjFcYlwAxf3u0likfsJUcMi5kbnNjcnlwdC1jZXJ0LnNlY3VyZWRucy5ldQ",
udp: true,
},
{
// Yandex DNS
stampStr: "sdns://AQQAAAAAAAAAEDc3Ljg4LjguNzg6MTUzNTMg04TAccn3RmKvKszVe13MlxTUB7atNgHhrtwG1W1JYyciMi5kbnNjcnlwdC1jZXJ0LmJyb3dzZXIueWFuZGV4Lm5ldA",
udp: true,
},
}
for _, test := range stamps {
if test.udp {
checkDnsCryptServer(t, test.stampStr, "udp")
}
checkDnsCryptServer(t, test.stampStr, "tcp")
}
}
func checkDnsCryptServer(t *testing.T, stampStr string, proto string) {
client := Client{Proto: proto, Timeout: 10 * time.Second}
serverInfo, rtt, err := client.Dial(stampStr)
if err != nil {
t.Fatalf("Could not establish connection with %s", stampStr)
}
log.Printf("Established a connection with %s, rtt=%v, proto=%s", serverInfo.ProviderName, rtt, proto)
req := dns.Msg{}
req.Id = dns.Id()
req.RecursionDesired = true
req.Question = []dns.Question{
{Name: "google-public-dns-a.google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
}
reply, rtt, err := client.Exchange(&req, serverInfo)
if err != nil {
t.Fatalf("Couldn't talk to upstream %s: %s", serverInfo.ProviderName, err)
}
if len(reply.Answer) != 1 {
t.Fatalf("DNS upstream %s returned reply with wrong number of answers - %d", serverInfo.ProviderName, len(reply.Answer))
}
if a, ok := reply.Answer[0].(*dns.A); ok {
if !net.IPv4(8, 8, 8, 8).Equal(a.A) {
t.Fatalf("DNS upstream %s returned wrong answer instead of 8.8.8.8: %v", serverInfo.ProviderName, a.A)
}
} else {
t.Fatalf("DNS upstream %s returned wrong answer type instead of A: %v", serverInfo.ProviderName, reply.Answer[0])
}
log.Printf("Got proper response from %s, rtt=%v, proto=%s", serverInfo.ProviderName, rtt, proto)
}

13
go.mod Normal file
View File

@ -0,0 +1,13 @@
module github.com/ameshkov/dnscrypt
require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
github.com/jedisct1/go-dnsstamps v0.0.0-20180418170050-1e4999280f86
github.com/jedisct1/xsecretbox v0.0.0-20180508184500-7a679c0bcd9a
github.com/miekg/dns v1.1.1
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
golang.org/x/net v0.0.0-20181213202711-891ebc4b82d6 // indirect
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06 // indirect
)

18
go.sum Normal file
View File

@ -0,0 +1,18 @@
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
github.com/jedisct1/go-dnsstamps v0.0.0-20180418170050-1e4999280f86 h1:Olj4M6T1omUfx7yTTcnhLf4xo6gYMmRHSJIfeA1NZy0=
github.com/jedisct1/go-dnsstamps v0.0.0-20180418170050-1e4999280f86/go.mod h1:j/ONpSHHmPgDwmFKXg9vhQvIjADe/ft1X4a3TVOmp9g=
github.com/jedisct1/xsecretbox v0.0.0-20180508184500-7a679c0bcd9a h1:2nyBWKszM41RO/gt5ElUXigAFiRgJ9KifHDlWOlw0lc=
github.com/jedisct1/xsecretbox v0.0.0-20180508184500-7a679c0bcd9a/go.mod h1:YlN58h704uRFD0BwsEGTq+7Wx+WG2i7P49bc+HwHyAY=
github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o=
github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20181213202711-891ebc4b82d6 h1:gT0Y6H7hbVPUtvtk0YGxMXPgN+p8fYlqWkgJeUCZcaQ=
golang.org/x/net v0.0.0-20181213202711-891ebc4b82d6/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06 h1:0oC8rFnE+74kEmuHZ46F6KHsMr5Gx2gUQPuNz28iQZM=
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=