Merge remote-tracking branch 'upstream/master'

# Conflicts:
#	dnstt-client/lib/dns.go
#	dnstt-server/main.go
This commit is contained in:
Benjamin Erhart
2026-05-05 14:15:52 +02:00
4 changed files with 104 additions and 23 deletions
+21
View File
@@ -1,3 +1,24 @@
## v1.20260501.0
Fixed an error in dnstt-server that could cause the server to stop
processing traffic, while still running. Rarely, a sendto system call in
the sendLoop function would cause sendLoop to return with an error.
Meanwhile, recvLoop would keep running and processing incoming queries,
but as sendLoop had finished, the server would cease to send back
responses. In live testing, one possible cause of a sendto error was the
Linux conntrack table becoming full from heavy traffic and the outgoing
packet being blocked by a default DENY rule in the local firewall; other
causes may have been possible. This release contains two changes to fix
the problem: (1) sendto errors, other than net.ErrClosed, are now only
logged and do not cause sendLoop to terminate; (2) if sendLoop (or any
other top-level goroutine) does return, it causes the whole process to
exit, which will make any failure noticeable and avoid the "running but
not working" failure mode.
## v1.20241021.0
Added a CC0 COPYING file.
## v1.20240513.0
Updated utls to v1.6.6. Added a "random" fingerprint ID that maps to
+37
View File
@@ -239,6 +239,43 @@ tunnel-client$ curl --proxy socks5h://127.0.0.1:7000/ https://wtfismyip.com/text
```
### Dante SOCKS proxy
With the Dante SOCKS proxy (https://www.inet.no/dante/), a minimal
configuration (/etc/danted.conf) may look like the following:
```
internal: lo port = 8000
external: eth0
clientmethod: none
socksmethod: none
user.privileged: proxy
user.unprivileged: nobody
client pass {
from: lo to: 0/0
}
socks block {
from: 0.0.0.0/0 to: lo
}
socks block {
from: ::/0 to: lo
}
socks pass {
from: lo to: 0.0.0.0/0
command: connect
}
```
See https://www.inet.no/dante/doc/latest/config/server.html for more
examples.
### Tor bridge
You can run a Tor bridge on the tunnel server and tunnel the connection
+6 -11
View File
@@ -187,11 +187,6 @@ 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
}
return err
}
@@ -257,29 +252,29 @@ func chunks(p []byte, n int) [][]byte {
//
// 0. Start with the raw packet contents.
//
// supercalifragilisticexpialidocious
// supercalifragilisticexpialidocious
//
// 1. Length-prefix the packet and add random padding. A length prefix L < 0xe0
// means a data packet of L bytes. A length prefix L ≥ 0xe0 means padding
// of L 0xe0 bytes (not counting the length of the length prefix itself).
//
// \xe3\xd9\xa3\x15\x22supercalifragilisticexpialidocious
// \xe3\xd9\xa3\x15\x22supercalifragilisticexpialidocious
//
// 2. Prefix the ClientID.
//
// CLIENTID\xe3\xd9\xa3\x15\x22supercalifragilisticexpialidocious
// CLIENTID\xe3\xd9\xa3\x15\x22supercalifragilisticexpialidocious
//
// 3. Base32-encode, without padding and in lower case.
//
// ingesrkokreujy6zumkse43vobsxey3bnruwm4tbm5uwy2ltoruwgzlyobuwc3djmrxwg2lpovzq
// ingesrkokreujy6zumkse43vobsxey3bnruwm4tbm5uwy2ltoruwgzlyobuwc3djmrxwg2lpovzq
//
// 4. Break into labels of at most 63 octets.
//
// ingesrkokreujy6zumkse43vobsxey3bnruwm4tbm5uwy2ltoruwgzlyobuwc3d.jmrxwg2lpovzq
// ingesrkokreujy6zumkse43vobsxey3bnruwm4tbm5uwy2ltoruwgzlyobuwc3d.jmrxwg2lpovzq
//
// 5. Append the domain.
//
// ingesrkokreujy6zumkse43vobsxey3bnruwm4tbm5uwy2ltoruwgzlyobuwc3d.jmrxwg2lpovzq.t.example.com
// ingesrkokreujy6zumkse43vobsxey3bnruwm4tbm5uwy2ltoruwgzlyobuwc3d.jmrxwg2lpovzq.t.example.com
func (c *DNSPacketConn) send(transport net.PacketConn, p []byte, addr net.Addr) error {
var decoded []byte
{
+40 -12
View File
@@ -269,6 +269,7 @@ func acceptStreams(conn *kcp.UDPSession, privkey []byte, upstream string) error
if err != nil {
//goland:noinspection GoDeprecation
if err, ok := err.(net.Error); ok && err.Temporary() {
log.Printf("AcceptStream temporary error: %v", err)
continue
}
return err
@@ -295,6 +296,7 @@ func acceptSessions(ln *kcp.Listener, privkey []byte, mtu int, upstream string)
if err != nil {
//goland:noinspection GoDeprecation
if err, ok := err.(net.Error); ok && err.Temporary() {
log.Printf("AcceptKCP temporary error: %v", err)
continue
}
return err
@@ -516,11 +518,6 @@ func recvLoop(domain dns.Name, dnsConn net.PacketConn, ttConn *turbotunnel.Queue
var buf [4096]byte
n, addr, err := dnsConn.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
}
return err
}
@@ -606,6 +603,7 @@ func sendLoop(dnsConn net.PacketConn, ttConn *turbotunnel.QueuePacketConn, ch <-
// overflow the capacity of the DNS response, we stash
// to be bundled into a future response.
timer := time.NewTimer(maxResponseDelay)
timerExpired := false
for {
var p []byte
unstash := ttConn.Unstash(rec.ClientID)
@@ -626,6 +624,7 @@ func sendLoop(dnsConn net.PacketConn, ttConn *turbotunnel.QueuePacketConn, ch <-
case p = <-unstash:
case p = <-outgoing:
case <-timer.C:
timerExpired = true
case nextRec = <-ch:
}
}
@@ -634,7 +633,11 @@ func sendLoop(dnsConn net.PacketConn, ttConn *turbotunnel.QueuePacketConn, ch <-
// only. The second and later packets must be
// immediately available or they will be omitted
// from this bundle.
if !timerExpired && !timer.Stop() {
<-timer.C
}
timer.Reset(0)
timerExpired = false
if len(p) == 0 {
// timer expired or receive on ch, we
@@ -660,7 +663,9 @@ func sendLoop(dnsConn net.PacketConn, ttConn *turbotunnel.QueuePacketConn, ch <-
_ = binary.Write(&payload, binary.BigEndian, uint16(len(p)))
payload.Write(p)
}
timer.Stop()
if !timerExpired && !timer.Stop() {
<-timer.C
}
rec.Resp.Answer[0].Data = dns.EncodeRDataTXT(payload.Bytes())
}
@@ -681,12 +686,14 @@ func sendLoop(dnsConn net.PacketConn, ttConn *turbotunnel.QueuePacketConn, ch <-
// Now we actually send the message as a UDP packet.
_, err = dnsConn.WriteTo(buf, rec.Addr)
if err != nil {
//goland:noinspection GoDeprecation
if err, ok := err.(net.Error); ok && err.Temporary() {
log.Printf("WriteTo temporary error: %v", err)
continue
// net.ErrClosed means we'll never be able to send on
// dnsConn, so terminate the loop. Treat all other
// errors as temporary and simply log them.
if errors.Is(err, net.ErrClosed) {
return err
}
return err
log.Printf("WriteTo error: %v", err)
continue
}
}
return nil
@@ -814,7 +821,15 @@ func run(privkey []byte, domain dns.Name, upstream string, dnsConn net.PacketCon
defer func() {
_ = ln.Close()
}()
// We will run acceptSessions, sendLoop, and recvLoop concurrently. The
// first one to finish closes the done channel.
doneChan := make(chan struct{})
var doneOnce sync.Once
done := func() { doneOnce.Do(func() { close(doneChan) }) }
go func() {
defer done()
err := acceptSessions(ln, privkey, mtu, upstream)
if err != nil {
log.Printf("acceptSessions: %v", err)
@@ -828,13 +843,26 @@ func run(privkey []byte, domain dns.Name, upstream string, dnsConn net.PacketCon
// for each response to collect downstream data before being evicted by
// another response that needs to be sent.
go func() {
defer done()
err := sendLoop(dnsConn, ttConn, ch, maxEncodedPayload)
if err != nil {
log.Printf("sendLoop: %v", err)
}
}()
return recvLoop(domain, dnsConn, ttConn, ch)
go func() {
defer done()
err := recvLoop(domain, dnsConn, ttConn, ch)
if err != nil {
log.Printf("recvLoop: %v", err)
}
}()
// Wait for any of acceptSessions, sendLoop, or recvLoop to return. In
// normal operation, we don't expect any of these to return.
<-doneChan
return nil
}
func main() {