diff --git a/common.go b/common.go index 84273a45..052d65c2 100644 --- a/common.go +++ b/common.go @@ -18,7 +18,8 @@ var ( ServerMagic = [8]byte{0x72, 0x36, 0x66, 0x6e, 0x76, 0x57, 0x6a, 0x38} MinDNSPacketSize = 12 MaxDNSPacketSize = 4096 - InitialMinQuestionSize = 128 + MaxDNSUDPPacketSize = 1252 + InitialMinQuestionSize = 256 TimeoutMin = 1 * time.Second TimeoutMax = 5 * time.Second ) @@ -36,3 +37,17 @@ type ServerInfo struct { func HasTCFlag(packet []byte) bool { return packet[2]&2 == 2 } + +func Min(a, b int) int { + if a < b { + return a + } + return b +} + +func Max(a, b int) int { + if a > b { + return a + } + return b +} diff --git a/crypto.go b/crypto.go index 37130a3f..7d761404 100644 --- a/crypto.go +++ b/crypto.go @@ -35,13 +35,27 @@ func unpad(packet []byte) ([]byte, error) { } } } -func (proxy *Proxy) Crypt(serverInfo *ServerInfo, packet []byte) (encrypted []byte, clientNonce []byte) { + +func (proxy *Proxy) Encrypt(serverInfo *ServerInfo, packet []byte, proto string) (encrypted []byte, clientNonce []byte, err error) { nonce, clientNonce := make([]byte, NonceSize), make([]byte, HalfNonceSize) rand.Read(clientNonce) copy(nonce, clientNonce) + minQuestionSize := len(packet) + if proto == "udp" { + minQuestionSize = proxy.questionSizeEstimator.MinQuestionSize() + } else { + var xpad [1]byte + rand.Read(xpad[:]) + minQuestionSize += int(xpad[0]) + } + paddedLength := Min((minQuestionSize+63)&^63, MaxDNSUDPPacketSize-1) + if paddedLength <= 0 || len(packet) >= paddedLength { + err = errors.New("Question too large; cannot be padded") + return + } encrypted = append(serverInfo.MagicQuery[:], proxy.proxyPublicKey[:]...) encrypted = append(encrypted, nonce[:HalfNonceSize]...) - encrypted = xsecretbox.Seal(encrypted, nonce, pad(packet, proxy.minQuestionSize), serverInfo.SharedKey[:]) + encrypted = xsecretbox.Seal(encrypted, nonce, pad(packet, paddedLength), serverInfo.SharedKey[:]) return } diff --git a/dnscrypt-proxy.go b/dnscrypt-proxy.go index 2266d086..553d16c9 100644 --- a/dnscrypt-proxy.go +++ b/dnscrypt-proxy.go @@ -2,6 +2,7 @@ package main import ( "crypto/rand" + "encoding/binary" "encoding/hex" "fmt" "log" @@ -14,71 +15,113 @@ import ( ) type Proxy struct { - proxyPublicKey [32]byte - proxySecretKey [32]byte - minQuestionSize int - serversInfo []ServerInfo + proxyPublicKey [32]byte + proxySecretKey [32]byte + questionSizeEstimator QuestionSizeEstimator + serversInfo []ServerInfo + timeout time.Duration } func main() { log.SetFlags(0) - _ = NewProxy("127.0.0.1:5399", "212.47.228.136:443", "E801:B84E:A606:BFB0:BAC0:CE43:445B:B15E:BA64:B02F:A3C4:AA31:AE10:636A:0790:324D", "2.dnscrypt-cert.fr.dnscrypt.org") + NewProxy("127.0.0.1:5399", "212.47.228.136:443", "E801:B84E:A606:BFB0:BAC0:CE43:445B:B15E:BA64:B02F:A3C4:AA31:AE10:636A:0790:324D", "2.dnscrypt-cert.fr.dnscrypt.org") } -func NewProxy(listenAddrStr string, serverAddrStr string, serverPkStr string, providerName string) Proxy { - proxy := Proxy{minQuestionSize: InitialMinQuestionSize} +func NewProxy(listenAddrStr string, serverAddrStr string, serverPkStr string, providerName string) { + proxy := Proxy{questionSizeEstimator: NewQuestionSizeEstimator(), timeout: TimeoutMax} if _, err := rand.Read(proxy.proxySecretKey[:]); err != nil { log.Fatal(err) } curve25519.ScalarBaseMult(&proxy.proxyPublicKey, &proxy.proxySecretKey) proxy.fetchServerInfo(serverAddrStr, serverPkStr, providerName) - proxy.udpListener(listenAddrStr) - return proxy -} - -func (proxy *Proxy) adjustMinQuestionSize() { - if MaxDNSPacketSize-proxy.minQuestionSize < proxy.minQuestionSize { - proxy.minQuestionSize = MaxDNSPacketSize - } else { - proxy.minQuestionSize *= 2 + listenUDPAddr, err := net.ResolveUDPAddr("udp", listenAddrStr) + if err != nil { + log.Fatal(err) + } + listenTCPAddr, err := net.ResolveTCPAddr("tcp", listenAddrStr) + if err != nil { + log.Fatal(err) + } + go func() { + proxy.udpListener(listenUDPAddr) + }() + go func() { + proxy.tcpListener(listenTCPAddr) + }() + for { + time.Sleep(30 * time.Minute) + // Refresh certificates } } -func (proxy *Proxy) udpListener(listenAddrStr string) error { - clientPc, err := net.ListenPacket("udp", listenAddrStr) +func (proxy *Proxy) udpListener(listenAddr *net.UDPAddr) error { + clientPc, err := net.ListenUDP("udp", listenAddr) if err != nil { return err } defer clientPc.Close() - fmt.Printf("Now listening to %v [UDP]\n", listenAddrStr) + fmt.Printf("Now listening to %v [UDP]\n", listenAddr) for { - buffer := make([]byte, MaxDNSPacketSize) + buffer := make([]byte, MaxDNSPacketSize-1) length, clientAddr, err := clientPc.ReadFrom(buffer) if err != nil { return err } packet := buffer[:length] go func() { - proxy.processIncomingUDPQuery(&proxy.serversInfo[0], packet, clientAddr, clientPc) + proxy.processIncomingQuery(&proxy.serversInfo[0], packet, &clientAddr, clientPc) }() } } -func (proxy *Proxy) processIncomingUDPQuery(serverInfo *ServerInfo, packet []byte, clientAddr net.Addr, clientPc net.PacketConn) { +func (proxy *Proxy) tcpListener(listenAddr *net.TCPAddr) error { + acceptPc, err := net.ListenTCP("tcp", listenAddr) + if err != nil { + return err + } + defer acceptPc.Close() + fmt.Printf("Now listening to %v [TCP]\n", listenAddr) + for { + clientPc, err := acceptPc.Accept() + if err != nil { + continue + } + go func() { + defer clientPc.Close() + clientPc.SetDeadline(time.Now().Add(proxy.timeout)) + buffer := make([]byte, 2+MaxDNSPacketSize-1) + length, err := clientPc.Read(buffer) + if err != nil { + return + } + innerLength := binary.BigEndian.Uint16(buffer[0:2]) + if int(innerLength) > length-2 { + return + } + packet := buffer[2:length] + proxy.processIncomingQuery(&proxy.serversInfo[0], packet, nil, clientPc) + }() + } +} + +func (proxy *Proxy) processIncomingQuery(serverInfo *ServerInfo, packet []byte, clientAddr *net.Addr, clientPc net.Conn) { if len(packet) < MinDNSPacketSize { return } - encrypted, clientNonce := proxy.Crypt(serverInfo, packet) + encrypted, clientNonce, err := proxy.Encrypt(serverInfo, packet, "udp") + if err != nil { + return + } pc, err := net.DialUDP("udp", nil, serverInfo.UDPAddr) if err != nil { return } - defer pc.Close() pc.SetDeadline(time.Now().Add(serverInfo.Timeout)) pc.Write(encrypted) encrypted = make([]byte, MaxDNSPacketSize) length, err := pc.Read(encrypted) + pc.Close() if err != nil { return } @@ -87,9 +130,16 @@ func (proxy *Proxy) processIncomingUDPQuery(serverInfo *ServerInfo, packet []byt if err != nil { return } - clientPc.WriteTo(packet, clientAddr) + if clientAddr != nil { + clientPc.(net.PacketConn).WriteTo(packet, *clientAddr) + } else { + packet = append(append(packet, 0), 0) + copy(packet[2:], packet[:len(packet)-2]) + binary.BigEndian.PutUint16(packet[0:2], uint16(len(packet)-2)) + clientPc.Write(packet) + } if HasTCFlag(packet) { - proxy.adjustMinQuestionSize() + proxy.questionSizeEstimator.blindAdjust() } } diff --git a/estimators.go b/estimators.go new file mode 100644 index 00000000..bfb732ef --- /dev/null +++ b/estimators.go @@ -0,0 +1,29 @@ +package main + +import "sync" + +type QuestionSizeEstimator struct { + sync.RWMutex + minQuestionSize int +} + +func NewQuestionSizeEstimator() QuestionSizeEstimator { + return QuestionSizeEstimator{minQuestionSize: InitialMinQuestionSize} +} + +func (questionSizeEstimator *QuestionSizeEstimator) MinQuestionSize() int { + questionSizeEstimator.RLock() + minQuestionSize := questionSizeEstimator.minQuestionSize + questionSizeEstimator.RUnlock() + return minQuestionSize +} + +func (questionSizeEstimator *QuestionSizeEstimator) blindAdjust() { + questionSizeEstimator.Lock() + if MaxDNSPacketSize-questionSizeEstimator.minQuestionSize < questionSizeEstimator.minQuestionSize { + questionSizeEstimator.minQuestionSize = MaxDNSUDPPacketSize + } else { + questionSizeEstimator.minQuestionSize *= 2 + } + questionSizeEstimator.Unlock() +}