Moved most files of the client into a subdirectory, to reduce need for patching for use in IPtProxy.

This commit is contained in:
Benjamin Erhart
2022-06-10 13:41:36 +02:00
parent f7aff04bef
commit f67f357a1e
10 changed files with 67 additions and 59 deletions
+17 -16
View File
@@ -1,4 +1,4 @@
package main
package dnstt_client
import (
"bytes"
@@ -16,8 +16,8 @@ import (
)
const (
// How many bytes of random padding to insert into queries.
numPadding = 3
// NumPadding How many bytes of random padding to insert into queries.
NumPadding = 3
// In an otherwise empty polling query, insert even more random padding,
// to reduce the chance of a cache hit. Cannot be greater than 31,
// because the prefix codes indicating padding start at 224.
@@ -39,8 +39,8 @@ const (
pollLimit = 16
)
// base32Encoding is a base32 encoding without padding.
var base32Encoding = base32.StdEncoding.WithPadding(base32.NoPadding)
// Base32Encoding is a base32 encoding without padding.
var Base32Encoding = base32.StdEncoding.WithPadding(base32.NoPadding)
// DNSPacketConn provides a packet-sending and -receiving interface over various
// forms of DNS. It handles the details of how packets and padding are encoded
@@ -187,6 +187,7 @@ func (c *DNSPacketConn) recvLoop(transport net.PacketConn) error {
var buf [4096]byte
n, addr, err := transport.ReadFrom(buf[:])
if err != nil {
//goland:noinspection GoDeprecation
if err, ok := err.(net.Error); ok && err.Temporary() {
log.Printf("ReadFrom temporary error: %v", err)
continue
@@ -205,13 +206,13 @@ func (c *DNSPacketConn) recvLoop(transport net.PacketConn) error {
// Pull out the packets contained in the payload.
r := bytes.NewReader(payload)
any := false
anyPacket := false
for {
p, err := nextPacket(r)
if err != nil {
break
}
any = true
anyPacket = true
c.QueuePacketConn.QueueIncoming(p, addr)
}
@@ -219,7 +220,7 @@ func (c *DNSPacketConn) recvLoop(transport net.PacketConn) error {
// to poll immediately. ACKs on received data will effectively
// serve as another stream of polls whose rate is proportional
// to the rate of incoming packets.
if any {
if anyPacket {
select {
case c.pollChan <- struct{}{}:
default:
@@ -228,9 +229,9 @@ func (c *DNSPacketConn) recvLoop(transport net.PacketConn) error {
}
}
// chunks breaks p into non-empty subslices of at most n bytes, greedily so that
// Chunks breaks p into non-empty subslices of at most n bytes, greedily so that
// only final subslice has length < n.
func chunks(p []byte, n int) [][]byte {
func Chunks(p []byte, n int) [][]byte {
var result [][]byte
for len(p) > 0 {
sz := len(p)
@@ -274,13 +275,13 @@ func (c *DNSPacketConn) send(transport net.PacketConn, p []byte, addr net.Addr)
var buf bytes.Buffer
// ClientID
buf.Write(c.clientID[:])
n := numPadding
n := NumPadding
if len(p) == 0 {
n = numPaddingForPoll
}
// Padding / cache inhibition
buf.WriteByte(byte(224 + n))
io.CopyN(&buf, rand.Reader, int64(n))
_, _ = io.CopyN(&buf, rand.Reader, int64(n))
// Packet contents
if len(p) > 0 {
buf.WriteByte(byte(len(p)))
@@ -289,10 +290,10 @@ func (c *DNSPacketConn) send(transport net.PacketConn, p []byte, addr net.Addr)
decoded = buf.Bytes()
}
encoded := make([]byte, base32Encoding.EncodedLen(len(decoded)))
base32Encoding.Encode(encoded, decoded)
encoded := make([]byte, Base32Encoding.EncodedLen(len(decoded)))
Base32Encoding.Encode(encoded, decoded)
encoded = bytes.ToLower(encoded)
labels := chunks(encoded, 63)
labels := Chunks(encoded, 63)
labels = append(labels, c.domain...)
name, err := dns.NewName(labels)
if err != nil {
@@ -300,7 +301,7 @@ func (c *DNSPacketConn) send(transport net.PacketConn, p []byte, addr net.Addr)
}
var id uint16
binary.Read(rand.Reader, binary.BigEndian, &id)
_ = binary.Read(rand.Reader, binary.BigEndian, &id)
query := &dns.Message{
ID: id,
Flags: 0x0100, // QR = 0, RD = 1
@@ -1,4 +1,4 @@
package main
package dnstt_client
import (
"bytes"
@@ -1,4 +1,4 @@
package main
package dnstt_client
import (
"bytes"
@@ -88,7 +88,9 @@ func (c *HTTPPacketConn) send(p []byte) error {
if err != nil {
return err
}
defer resp.Body.Close()
defer func() {
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
@@ -1,4 +1,4 @@
package main
package dnstt_client
import (
"testing"
@@ -1,4 +1,4 @@
package main
package dnstt_client
import (
"bufio"
@@ -54,7 +54,9 @@ func NewTLSPacketConn(addr string, dialTLSContext func(ctx context.Context, netw
QueuePacketConn: turbotunnel.NewQueuePacketConn(turbotunnel.DummyAddr{}, 0),
}
go func() {
defer c.Close()
defer func() {
_ = c.Close()
}()
for {
var wg sync.WaitGroup
wg.Add(2)
@@ -73,7 +75,7 @@ func NewTLSPacketConn(addr string, dialTLSContext func(ctx context.Context, netw
wg.Done()
}()
wg.Wait()
conn.Close()
_ = conn.Close()
// Whenever the TLS connection dies, redial a new one.
conn, err = dial()
@@ -1,4 +1,4 @@
package main
package dnstt_client
// Support code for TLS camouflage using uTLS.
@@ -16,9 +16,9 @@ import (
"golang.org/x/net/http2"
)
// utlsClientHelloIDMap is a correspondence between human-readable labels and
// UtlsClientHelloIDMap is a correspondence between human-readable labels and
// supported utls.ClientHelloIDs.
var utlsClientHelloIDMap = []struct {
var UtlsClientHelloIDMap = []struct {
Label string
ID *utls.ClientHelloID
}{
@@ -38,10 +38,10 @@ var utlsClientHelloIDMap = []struct {
{"iOS_12_1", &utls.HelloIOS_12_1},
}
// utlsLookup returns a *utls.ClientHelloID from utlsClientHelloIDMap by a
// UtlsLookup returns a *utls.ClientHelloID from utlsClientHelloIDMap by a
// case-insensitive label match, or nil if there is no match.
func utlsLookup(label string) *utls.ClientHelloID {
for _, entry := range utlsClientHelloIDMap {
func UtlsLookup(label string) *utls.ClientHelloID {
for _, entry := range UtlsClientHelloIDMap {
if strings.ToLower(label) == strings.ToLower(entry.Label) {
return entry.ID
}
@@ -49,10 +49,10 @@ func utlsLookup(label string) *utls.ClientHelloID {
return nil
}
// utlsDialContext connects to the given network address and initiates a TLS
// UtlsDialContext connects to the given network address and initiates a TLS
// handshake with the provided ClientHelloID, and returns the resulting TLS
// connection.
func utlsDialContext(ctx context.Context, network, addr string, config *utls.Config, id *utls.ClientHelloID) (*utls.UConn, error) {
func UtlsDialContext(ctx context.Context, network, addr string, config *utls.Config, id *utls.ClientHelloID) (*utls.UConn, error) {
// Set the SNI from addr, if not already set.
if config == nil {
config = &utls.Config{}
@@ -76,7 +76,7 @@ func utlsDialContext(ctx context.Context, network, addr string, config *utls.Con
if net.ParseIP(config.ServerName) != nil {
err := uconn.RemoveSNIExtension()
if err != nil {
uconn.Close()
_ = uconn.Close()
return nil, err
}
}
@@ -88,7 +88,7 @@ func utlsDialContext(ctx context.Context, network, addr string, config *utls.Con
// https://github.com/refraction-networking/utls/issues/75
err = uconn.Handshake()
if err != nil {
uconn.Close()
_ = uconn.Close()
return nil, err
}
return uconn, nil
@@ -186,7 +186,7 @@ func makeRoundTripper(req *http.Request, config *utls.Config, id *utls.ClientHel
return nil, err
}
bootstrapConn, err := utlsDialContext(req.Context(), "tcp", addr, config, id)
bootstrapConn, err := UtlsDialContext(req.Context(), "tcp", addr, config, id)
if err != nil {
return nil, err
}
@@ -210,7 +210,7 @@ func makeRoundTripper(req *http.Request, config *utls.Config, id *utls.ClientHel
}
// Later dials make a new connection.
uconn, err := utlsDialContext(ctx, "tcp", addr, config, id)
uconn, err := UtlsDialContext(ctx, "tcp", addr, config, id)
if err != nil {
return nil, err
}
@@ -1,4 +1,4 @@
package main
package dnstt_client
// Random selection from weighted distributions, and strings for specifying such
// distributions.
@@ -12,7 +12,7 @@ import (
"strings"
)
// parseWeightedList parses a list of text labels with optional numeric weights,
// ParseWeightedList parses a list of text labels with optional numeric weights,
// and returns parallel slices of weights and labels. If a weight is omitted for
// a label, the weight is 1.
//
@@ -22,7 +22,7 @@ import (
//
// list ::= entry ("," entry)*
// entry ::= (weight "*")? label
func parseWeightedList(s string) ([]uint32, []string, error) {
func ParseWeightedList(s string) ([]uint32, []string, error) {
const (
kindEOF = iota
kindComma
@@ -176,10 +176,10 @@ func (s cryptoSource) Int63() int64 {
return n
}
// sampleWeighted returns the index of a randomly selected element of the
// SampleWeighted returns the index of a randomly selected element of the
// weights slice, weighted by the values stored in the slice. Panics if
// the sum of the weights is zero or does not fit in an int64.
func sampleWeighted(weights []uint32) int {
func SampleWeighted(weights []uint32) int {
var sum int64 = 0
for _, w := range weights {
sum += int64(w)
@@ -1,4 +1,4 @@
package main
package dnstt_client
import (
"testing"
@@ -20,7 +20,7 @@ func TestParseWeightedList(t *testing.T) {
{"\\,", []uint32{1}, []string{","}},
{"3\\*apple\\,car\\rot,100*orange", []uint32{1, 100}, []string{"3*apple,carrot", "orange"}},
} {
weights, labels, err := parseWeightedList(test.input)
weights, labels, err := ParseWeightedList(test.input)
if err != nil {
t.Errorf("%+q resulted in error: %v", test.input, err)
continue
@@ -55,7 +55,7 @@ func TestParseWeightedList(t *testing.T) {
"-5*apple",
"5.5*apple",
} {
_, _, err := parseWeightedList(input)
_, _, err := ParseWeightedList(input)
if err == nil {
t.Errorf("%+q resulted in no error", input)
continue
@@ -77,7 +77,7 @@ func TestSampleWeighted(t *testing.T) {
t.Errorf("%v: expected panic", weights)
}
}()
sampleWeighted(weights)
SampleWeighted(weights)
}()
}
@@ -92,7 +92,7 @@ func TestSampleWeighted(t *testing.T) {
{[]uint32{0, 0, 0xffffffff, 0, 1}, 2},
} {
for i := 0; i < 100; i++ {
index := sampleWeighted(test.weights)
index := SampleWeighted(test.weights)
if index != test.index {
t.Errorf("%v: expected %d, got %d", test.weights, test.index, index)
}
+13 -11
View File
@@ -48,6 +48,7 @@ import (
"sync"
"syscall"
"time"
dc "www.bamsoftware.com/git/dnstt.git/dnstt-client/lib"
utls "github.com/refraction-networking/utls"
"github.com/xtaci/kcp-go/v5"
@@ -99,7 +100,7 @@ func readKeyFromFile(filename string) ([]byte, error) {
// utls.ClientHelloID from utlsClientHelloIDMap, and randomly samples one
// utls.ClientHelloID from the distribution.
func sampleUTLSDistribution(spec string) (*utls.ClientHelloID, error) {
weights, labels, err := parseWeightedList(spec)
weights, labels, err := dc.ParseWeightedList(spec)
if err != nil {
return nil, err
}
@@ -109,14 +110,14 @@ func sampleUTLSDistribution(spec string) (*utls.ClientHelloID, error) {
if label == "none" {
id = nil
} else {
id = utlsLookup(label)
id = dc.UtlsLookup(label)
if id == nil {
return nil, fmt.Errorf("unknown TLS fingerprint %q", label)
}
}
ids = append(ids, id)
}
return ids[sampleWeighted(weights)], nil
return ids[dc.SampleWeighted(weights)], nil
}
func handle(local *net.TCPConn, sess *smux.Session, conv uint32) error {
@@ -170,7 +171,7 @@ func listen(pubkey []byte, domain dns.Name, remoteAddr net.Addr, pconn net.Packe
return nil, nil, nil, fmt.Errorf("opening local listener: %v", err)
}
mtu := dnsNameCapacity(domain) - 8 - 1 - numPadding - 1 // clientid + padding length prefix + padding + data length prefix
mtu := dnsNameCapacity(domain) - 8 - 1 - dc.NumPadding - 1 // clientid + padding length prefix + padding + data length prefix
if mtu < 80 {
_ = pconn.Close()
_ = ln.Close()
@@ -244,6 +245,7 @@ func acceptLoop(ln *pt.SocksListener, pconn net.PacketConn, conn *kcp.UDPSession
for {
local, err := ln.AcceptSocks()
if err != nil {
//goland:noinspection GoDeprecation
if err, ok := err.(net.Error); ok && err.Temporary() {
continue
}
@@ -308,9 +310,9 @@ Examples:
`, os.Args[0])
flag.PrintDefaults()
labels := make([]string, 0, len(utlsClientHelloIDMap))
labels := make([]string, 0, len(dc.UtlsClientHelloIDMap))
labels = append(labels, "none")
for _, entry := range utlsClientHelloIDMap {
for _, entry := range dc.UtlsClientHelloIDMap {
labels = append(labels, entry.Label)
}
_, _ = fmt.Fprintf(flag.CommandLine.Output(), `
@@ -407,9 +409,9 @@ Known TLS fingerprints for -utls are:
transport.Proxy = nil
rt = transport
} else {
rt = NewUTLSRoundTripper(nil, utlsClientHelloID)
rt = dc.NewUTLSRoundTripper(nil, utlsClientHelloID)
}
pconn, err := NewHTTPPacketConn(rt, dohURL, 32)
pconn, err := dc.NewHTTPPacketConn(rt, dohURL, 32)
return addr, pconn, err
}},
// -dot
@@ -420,10 +422,10 @@ Known TLS fingerprints for -utls are:
dialTLSContext = (&tls.Dialer{}).DialContext
} else {
dialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
return utlsDialContext(ctx, network, addr, nil, utlsClientHelloID)
return dc.UtlsDialContext(ctx, network, addr, nil, utlsClientHelloID)
}
}
pconn, err := NewTLSPacketConn(dotAddr, dialTLSContext)
pconn, err := dc.NewTLSPacketConn(dotAddr, dialTLSContext)
return addr, pconn, err
}},
// -udp
@@ -473,7 +475,7 @@ Known TLS fingerprints for -utls are:
for _, methodName := range ptInfo.MethodNames {
switch methodName {
case "dnstt":
pconn = NewDNSPacketConn(pconn, remoteAddr, domain)
pconn = dc.NewDNSPacketConn(pconn, remoteAddr, domain)
ln, conn, sess, err := listen(pubkey, domain, remoteAddr, pconn)
if err != nil {
_ = pt.CmethodError(methodName, err.Error())
+4 -3
View File
@@ -3,13 +3,14 @@ package main
import (
"bytes"
"testing"
dc "www.bamsoftware.com/git/dnstt.git/dnstt-client/lib"
"www.bamsoftware.com/git/dnstt.git/dns"
)
func TestDNSNameCapacity(t *testing.T) {
for domainLen := 0; domainLen < 255; domainLen++ {
domain, err := dns.NewName(chunks(bytes.Repeat([]byte{'x'}, domainLen), 63))
domain, err := dns.NewName(dc.Chunks(bytes.Repeat([]byte{'x'}, domainLen), 63))
if err != nil {
continue
}
@@ -17,8 +18,8 @@ func TestDNSNameCapacity(t *testing.T) {
if capacity <= 0 {
continue
}
prefix := []byte(base32Encoding.EncodeToString(bytes.Repeat([]byte{'y'}, capacity)))
labels := append(chunks(prefix, 63), domain...)
prefix := []byte(dc.Base32Encoding.EncodeToString(bytes.Repeat([]byte{'y'}, capacity)))
labels := append(dc.Chunks(prefix, 63), domain...)
_, err = dns.NewName(labels)
if err != nil {
t.Errorf("length %v capacity %v %v", domainLen, capacity, err)