This commit is contained in:
2026-08-16 19:02:48 -03:00
parent 96fe00eb2b
commit c8e3011f21
31 changed files with 3457 additions and 351 deletions
+99 -64
View File
@@ -15,11 +15,32 @@ import (
"time"
"dragontcp/internal/protocol"
"dragontcp/internal/wire"
)
var active int64
// idleDeadline avoids a SetDeadline system call for every small protocol
// record. It refreshes halfway through the idle window, preserving idle-client
// cleanup while making persistent high-throughput lanes substantially cheaper.
type idleDeadline struct {
conn net.Conn
timeout time.Duration
next time.Time
}
func newIdleDeadline(conn net.Conn, timeout time.Duration) *idleDeadline {
return &idleDeadline{conn: conn, timeout: timeout}
}
func (d *idleDeadline) refresh() error {
now := time.Now()
if !d.next.IsZero() && now.Before(d.next.Add(-d.timeout/2)) {
return nil
}
d.next = now.Add(d.timeout)
return d.conn.SetDeadline(d.next)
}
type dnsEntry struct {
ips []netip.Addr
expires time.Time
@@ -160,10 +181,11 @@ func handle(
tcpBuffer int,
slots chan struct{},
manager *streamManager,
bhttpManager *bhttpSessionManager,
xorManager *chunkManager,
chunkMax int,
bufferBytes int,
chunkBuffered int,
xorBufferBytes int,
chunkPollWait time.Duration,
debug *serverDebug,
) {
@@ -176,46 +198,71 @@ func handle(
protocol.TuneTCP(conn)
protocol.TuneTCPBuffer(conn, tcpBuffer)
// One listener serves both wires. The legacy XOR framing starts every
// request with the ASCII magic "UP"; the binary framing starts with a mode
// byte of 0-4, so the two are never ambiguous.
// One listener serves both wires and every startup-selected header profile.
// sniffWire partitions the full first-byte space so B and X remain
// unambiguous even when their legacy mode/UP bytes are masked.
_ = conn.SetDeadline(time.Now().Add(30 * time.Second))
conn, isXOR, err := sniffWire(conn)
conn, isXOR, headerMask, err := sniffWire(conn)
if err != nil {
return
}
if isXOR {
if debug != nil && debug.enabled {
debug.logf("WIRE peer=%v mode=xor", conn.RemoteAddr())
debug.logf("WIRE peer=%v mode=xor header_mask=%02x", conn.RemoteAddr(), headerMask)
}
handleXOR(conn, token, allowPrivate, cache, tcpBuffer, xorManager,
chunkMax, chunkBuffered, chunkPollWait, debug)
handleXOR(conn, headerMask, token, allowPrivate, cache, tcpBuffer, xorManager,
chunkMax, xorBufferBytes, chunkPollWait, debug)
return
}
clearPayload := false
if profiled, ok := conn.(interface{ ClearPayload() bool }); ok {
clearPayload = profiled.ClearPayload()
}
if debug != nil && debug.enabled {
debug.logf("WIRE peer=%v mode=binary", conn.RemoteAddr())
debug.logf("WIRE peer=%v mode=binary header_mask=%02x clear_payload=%t", conn.RemoteAddr(), headerMask, clearPayload)
}
handleBinary(conn, headerMask, clearPayload, token, allowPrivate, cache, tcpBuffer, manager,
bhttpManager, chunkMax, bufferBytes, chunkPollWait, debug)
}
func acceptLoop(
ln net.Listener,
token string,
allowPrivate bool,
cache *dnsCache,
tcpBuffer int,
slots chan struct{},
manager *streamManager,
bhttpManager *bhttpSessionManager,
xorManager *chunkManager,
chunkMax int,
bufferBytes int,
xorBufferBytes int,
chunkPollWait time.Duration,
debug *serverDebug,
) {
for {
_ = conn.SetDeadline(time.Now().Add(30 * time.Second))
req, err := wire.ReadRequest(conn)
conn, err := ln.Accept()
if err != nil {
return
fmt.Fprintln(os.Stderr, "accept:", err)
continue
}
if err := processWireRequest(
conn,
req,
token,
allowPrivate,
cache,
tcpBuffer,
manager,
chunkMax,
bufferBytes,
chunkPollWait,
debug,
); err != nil {
return
select {
case slots <- struct{}{}:
atomic.AddInt64(&active, 1)
if debug.enabled {
debug.logf("ACCEPT local=%v peer=%v active_connections=%d", conn.LocalAddr(), conn.RemoteAddr(), atomic.LoadInt64(&active))
}
go handle(conn, token, allowPrivate, cache, tcpBuffer, slots, manager,
bhttpManager, xorManager, chunkMax, bufferBytes, xorBufferBytes,
chunkPollWait, debug)
default:
if debug.enabled {
debug.errorf("REJECT peer=%v reason=max-connections", conn.RemoteAddr())
}
_ = conn.Close()
}
}
}
@@ -224,6 +271,7 @@ func main() {
var (
host = flag.String("host", "0.0.0.0", "listen host")
port = flag.Int("port", 53, "listen port")
portAlt = flag.Int("port-alt", 80, "second simultaneous listen port; 0 disables")
token = flag.String("token", "", "optional shared token")
maxConnections = flag.Int("max-connections", 20000, "max simultaneous tunnels")
allowPrivate = flag.Bool("allow-private", false, "allow private/loopback targets")
@@ -231,7 +279,7 @@ func main() {
dnsCacheSize = flag.Int("dns-cache-size", 4096, "maximum cached DNS hostnames")
tcpBuffer = flag.Int("tcp-buffer", 0, "optional TCP read/write buffer bytes; 0 keeps OS autotuning")
chunkMax = flag.Int("chunk-max", 1048576, "maximum adaptive chunk payload bytes (32 bytes to 1 MiB)")
chunkBuffered = flag.Int("chunk-buffered", 256, "compatibility buffer units; 256 = about 16 MiB per active session")
chunkBuffered = flag.Int("chunk-buffered", 32, "per-session download buffer in 64 KiB units; 32 = about 2 MiB")
chunkPollWait = flag.Duration("chunk-poll-wait", 200*time.Millisecond, "server long-poll wait for chunk data")
sessionTimeout = flag.Duration("chunk-session-timeout", 2*time.Minute, "idle chunk session timeout")
debugEnabled = flag.Bool("debug", false, "log session/connect/errors and periodic statistics")
@@ -256,8 +304,23 @@ func main() {
os.Exit(1)
}
defer ln.Close()
listeners := []net.Listener{ln}
fmt.Printf("DragonTCP Go server listening on %s\n", listenAddr)
if *portAlt < 0 || *portAlt > 65535 {
fmt.Fprintln(os.Stderr, "--port-alt must be between 0 and 65535")
os.Exit(2)
}
if *portAlt != 0 && *portAlt != *port {
altAddr := net.JoinHostPort(*host, strconv.Itoa(*portAlt))
alt, altErr := net.Listen("tcp", altAddr)
if altErr != nil {
fmt.Fprintf(os.Stderr, "warning: secondary listener %s unavailable: %v\n", altAddr, altErr)
} else {
defer alt.Close()
listeners = append(listeners, alt)
fmt.Printf("DragonTCP Go server listening on %s\n", altAddr)
}
}
fmt.Printf("max_connections=%d tcp_buffer=%d\n", *maxConnections, *tcpBuffer)
slots := make(chan struct{}, *maxConnections)
@@ -271,45 +334,17 @@ func main() {
bufferBytes = 64 * 1024 * 1024
}
manager := newStreamManager(*sessionTimeout, debug)
bhttpManager := newBHTTPSessionManager(*sessionTimeout, *maxConnections)
xorManager := newChunkManager(*sessionTimeout, debug)
fmt.Printf("binary_transport=true chunk_max=%d buffer_bytes=%d poll_wait=%s\n", *chunkMax, bufferBytes, chunkPollWait.String())
fmt.Printf("binary_transport=true bp_compat=true chunk_max=%d buffer_bytes=%d poll_wait=%s\n", *chunkMax, bufferBytes, chunkPollWait.String())
if debug.enabled {
fmt.Printf("debug=true debug_chunks=%t stats_interval=%s\n", debug.chunks, debug.statsEvery)
}
for {
conn, err := ln.Accept()
if err != nil {
fmt.Fprintln(os.Stderr, "accept:", err)
continue
}
select {
case slots <- struct{}{}:
atomic.AddInt64(&active, 1)
if debug.enabled {
debug.logf("ACCEPT peer=%v active_connections=%d", conn.RemoteAddr(), atomic.LoadInt64(&active))
}
go handle(
conn,
*token,
*allowPrivate,
cache,
*tcpBuffer,
slots,
manager,
xorManager,
*chunkMax,
bufferBytes,
*chunkBuffered,
*chunkPollWait,
debug,
)
default:
if debug.enabled {
debug.errorf("REJECT peer=%v reason=max-connections", conn.RemoteAddr())
}
_ = conn.Close()
}
for _, listener := range listeners {
go acceptLoop(listener, *token, *allowPrivate, cache, *tcpBuffer, slots,
manager, bhttpManager, xorManager, *chunkMax, bufferBytes,
bufferBytes, *chunkPollWait, debug)
}
select {}
}