This commit is contained in:
2026-08-16 02:50:24 -03:00
parent 6dac260155
commit 5621de243a
8 changed files with 628 additions and 353 deletions
+203 -105
View File
@@ -1,6 +1,7 @@
package main
import (
"bytes"
"crypto/subtle"
"encoding/hex"
"errors"
@@ -36,6 +37,8 @@ type debugStats struct {
activeSessions atomic.Int64
upPackets atomic.Uint64
downPackets atomic.Uint64
upBatches atomic.Uint64
downBatches atomic.Uint64
upBytes atomic.Uint64
downBytes atomic.Uint64
dropped atomic.Uint64
@@ -69,20 +72,27 @@ func tokenEqual(a, b string) bool {
}
type vpnSession struct {
sid protocol.VPNSessionID
ipv4 netip.Addr
ipv6 netip.Addr
mtu int
maxChunk int
maxPackets int
manager *vpnManager
sid protocol.VPNSessionID
ipv4 netip.Addr
ipv6 netip.Addr
mtu int
maxChunk int
maxPackets int
maxQueueBytes int
batchDelay time.Duration
manager *vpnManager
mu sync.Mutex
notify chan struct{}
packets map[uint32][]byte
nextDown uint32
closed bool
lastSeen time.Time
mu sync.Mutex
notify chan struct{}
packets map[uint32]*downTransfer
nextDown uint32
closed bool
lastSeen time.Time
pendingPackets [][]byte
pendingEncoded int
pendingTimer *time.Timer
queuedPacketCount int
queuedBytes int
upMu sync.Mutex
expectedUp uint32
@@ -95,10 +105,16 @@ type vpnSession struct {
haveLastComplete bool
}
func newVPNSession(m *vpnManager, sid protocol.VPNSessionID, v4, v6 netip.Addr, mtu, maxChunk, maxPackets int) *vpnSession {
type downTransfer struct {
data []byte
packetCount int
rawBytes int
}
func newVPNSession(m *vpnManager, sid protocol.VPNSessionID, v4, v6 netip.Addr, mtu, maxChunk, maxPackets, maxQueueBytes int, batchDelay time.Duration) *vpnSession {
return &vpnSession{
sid: sid, ipv4: v4, ipv6: v6, mtu: mtu, maxChunk: maxChunk, maxPackets: maxPackets,
manager: m, notify: make(chan struct{}), packets: make(map[uint32][]byte, maxPackets), lastSeen: time.Now(),
sid: sid, ipv4: v4, ipv6: v6, mtu: mtu, maxChunk: maxChunk, maxPackets: maxPackets, maxQueueBytes: maxQueueBytes, batchDelay: batchDelay,
manager: m, notify: make(chan struct{}), packets: make(map[uint32]*downTransfer, maxPackets), lastSeen: time.Now(),
}
}
@@ -109,8 +125,54 @@ func (s *vpnSession) signalLocked() {
func (s *vpnSession) touchLocked() { s.lastSeen = time.Now() }
func (s *vpnSession) touch() { s.mu.Lock(); s.touchLocked(); s.mu.Unlock() }
func (s *vpnSession) flushPendingLocked() {
if len(s.pendingPackets) == 0 {
return
}
if s.pendingTimer != nil {
s.pendingTimer.Stop()
s.pendingTimer = nil
}
batch, err := protocol.BuildVPNBatch(s.pendingPackets)
if err != nil {
if s.manager.debug != nil {
s.manager.debug.dropped.Add(uint64(len(s.pendingPackets)))
s.manager.debug.errorf("BATCH sid=%s: %v", shortSID(s.sid), err)
}
s.queuedPacketCount -= len(s.pendingPackets)
for _, p := range s.pendingPackets {
s.queuedBytes -= len(p)
}
s.pendingPackets = nil
s.pendingEncoded = 0
return
}
rawBytes := 0
for _, p := range s.pendingPackets {
rawBytes += len(p)
}
seq := s.nextDown
s.nextDown++
s.packets[seq] = &downTransfer{data: batch, packetCount: len(s.pendingPackets), rawBytes: rawBytes}
if s.manager.debug != nil {
s.manager.debug.downBatches.Add(1)
s.manager.debug.packetf("BATCH QUEUE sid=%s seq=%d packets=%d raw_bytes=%d transfer_bytes=%d", shortSID(s.sid), seq, len(s.pendingPackets), rawBytes, len(batch))
}
s.pendingPackets = nil
s.pendingEncoded = 0
s.signalLocked()
}
func (s *vpnSession) flushPending() {
s.mu.Lock()
if !s.closed {
s.flushPendingLocked()
}
s.mu.Unlock()
}
func (s *vpnSession) enqueue(packet []byte) bool {
if len(packet) == 0 || len(packet) > 65535 {
if len(packet) == 0 || len(packet) > protocol.VPNMaxPacket {
return false
}
s.mu.Lock()
@@ -118,21 +180,39 @@ func (s *vpnSession) enqueue(packet []byte) bool {
if s.closed {
return false
}
if len(s.packets) >= s.maxPackets {
need := 2 + len(packet)
if len(s.pendingPackets) > 0 && s.pendingEncoded+need > protocol.VPNMaxBatch {
s.flushPendingLocked()
}
if s.queuedPacketCount >= s.maxPackets || s.queuedBytes+len(packet) > s.maxQueueBytes {
if s.manager.debug != nil {
s.manager.debug.dropped.Add(1)
}
return false
}
seq := s.nextDown
s.nextDown++
s.packets[seq] = append([]byte(nil), packet...)
p := append([]byte(nil), packet...)
if len(s.pendingPackets) == 0 {
s.pendingEncoded = 1
}
s.pendingPackets = append(s.pendingPackets, p)
s.pendingEncoded += 2 + len(p)
s.queuedPacketCount++
s.queuedBytes += len(p)
s.touchLocked()
s.signalLocked()
if s.manager.debug != nil {
s.manager.debug.downPackets.Add(1)
s.manager.debug.downBytes.Add(uint64(len(packet)))
s.manager.debug.packetf("QUEUE sid=%s seq=%d bytes=%d", shortSID(s.sid), seq, len(packet))
s.manager.debug.packetf("QUEUE sid=%s bytes=%d pending_packets=%d pending_transfer=%d", shortSID(s.sid), len(packet), len(s.pendingPackets), s.pendingEncoded)
}
if s.pendingEncoded >= protocol.VPNMaxBatch {
s.flushPendingLocked()
} else if s.pendingTimer == nil {
delay := s.batchDelay
if delay <= 0 {
s.flushPendingLocked()
} else {
s.pendingTimer = time.AfterFunc(delay, s.flushPending)
}
}
return true
}
@@ -140,8 +220,8 @@ func (s *vpnSession) enqueue(packet []byte) bool {
func (s *vpnSession) push(seq uint32, offset, total int, data []byte) (int, error) {
s.upMu.Lock()
defer s.upMu.Unlock()
if total < 1 || total > 65535 || len(data) < 1 || len(data) > s.maxChunk || offset < 0 || offset+len(data) > total {
return 0, errors.New("invalid packet fragment")
if total < 1 || total > protocol.VPNMaxBatch || len(data) < 1 || len(data) > s.maxChunk || offset < 0 || offset+len(data) > total {
return 0, errors.New("invalid transfer fragment")
}
if s.haveLastComplete && seq == s.lastComplete {
@@ -165,14 +245,14 @@ func (s *vpnSession) push(seq uint32, offset, total int, data []byte) (int, erro
s.currentBuf = make([]byte, 0, total)
}
if s.currentSeq != seq || s.currentTotal != total {
return 0, errors.New("packet fragment metadata changed")
return 0, errors.New("transfer fragment metadata changed")
}
// Idempotent retry: if this exact offset was already accepted, acknowledge
// the existing bytes instead of appending duplicate data.
if offset < len(s.currentBuf) {
end := offset + len(data)
if end <= len(s.currentBuf) && string(s.currentBuf[offset:end]) == string(data) {
if end <= len(s.currentBuf) && bytes.Equal(s.currentBuf[offset:end], data) {
return len(s.currentBuf), nil
}
return 0, errors.New("retry fragment does not match accepted data")
@@ -188,11 +268,11 @@ func (s *vpnSession) push(seq uint32, offset, total int, data []byte) (int, erro
return accepted, nil
}
packet := append([]byte(nil), s.currentBuf...)
transfer := append([]byte(nil), s.currentBuf...)
s.haveCurrent = false
s.currentBuf = nil
if err := s.manager.acceptClientPacket(s, packet); err != nil {
if err := s.manager.acceptClientTransfer(s, transfer); err != nil {
return 0, err
}
@@ -202,9 +282,8 @@ func (s *vpnSession) push(seq uint32, offset, total int, data []byte) (int, erro
s.expectedUp++
s.touch()
if s.manager.debug != nil {
s.manager.debug.upPackets.Add(1)
s.manager.debug.upBytes.Add(uint64(len(packet)))
s.manager.debug.packetf("UP sid=%s seq=%d bytes=%d", shortSID(s.sid), seq, len(packet))
s.manager.debug.upBatches.Add(1)
s.manager.debug.packetf("UP BATCH sid=%s seq=%d transfer_bytes=%d", shortSID(s.sid), seq, len(transfer))
}
return accepted, nil
}
@@ -221,21 +300,26 @@ func (s *vpnSession) pull(ack, want uint32, offset, limit int, wait time.Duratio
if ack != protocol.VPNNoAck {
for seq := range s.packets {
if seq <= ack {
rec := s.packets[seq]
if rec != nil {
s.queuedPacketCount -= rec.packetCount
s.queuedBytes -= rec.rawBytes
}
delete(s.packets, seq)
}
}
}
if packet, ok := s.packets[want]; ok {
if offset >= len(packet) {
if rec, ok := s.packets[want]; ok {
if offset >= len(rec.data) {
s.mu.Unlock()
return nil, len(packet), false, errors.New("pull offset beyond packet")
return nil, len(rec.data), false, errors.New("pull offset beyond transfer")
}
end := offset + limit
if end > len(packet) {
end = len(packet)
if end > len(rec.data) {
end = len(rec.data)
}
out := append([]byte(nil), packet[offset:end]...)
total := len(packet)
out := append([]byte(nil), rec.data[offset:end]...)
total := len(rec.data)
s.mu.Unlock()
return out, total, false, nil
}
@@ -257,35 +341,41 @@ func (s *vpnSession) close() {
s.mu.Lock()
if !s.closed {
s.closed = true
if s.pendingTimer != nil {
s.pendingTimer.Stop()
s.pendingTimer = nil
}
s.signalLocked()
}
s.mu.Unlock()
}
type vpnManager struct {
mu sync.RWMutex
sessions map[protocol.VPNSessionID]*vpnSession
byIPv4 map[netip.Addr]*vpnSession
byIPv6 map[netip.Addr]*vpnSession
maxChunk int
maxPackets int
pollWait time.Duration
timeout time.Duration
tun *os.File
tunWriteMu sync.Mutex
mockEcho bool
allowPrivate bool
debug *debugStats
v4Prefix netip.Prefix
v6Prefix netip.Prefix
mu sync.RWMutex
sessions map[protocol.VPNSessionID]*vpnSession
byIPv4 map[netip.Addr]*vpnSession
byIPv6 map[netip.Addr]*vpnSession
maxChunk int
maxPackets int
maxQueueBytes int
batchDelay time.Duration
pollWait time.Duration
timeout time.Duration
tun *os.File
tunWriteMu sync.Mutex
mockEcho bool
allowPrivate bool
debug *debugStats
v4Prefix netip.Prefix
v6Prefix netip.Prefix
}
func newVPNManager(tun *os.File, mockEcho bool, maxChunk, maxPackets int, pollWait, timeout time.Duration, allowPrivate bool, debug *debugStats) *vpnManager {
func newVPNManager(tun *os.File, mockEcho bool, maxChunk, maxPackets, maxQueueBytes int, pollWait, timeout, batchDelay time.Duration, allowPrivate bool, debug *debugStats) *vpnManager {
v4p := netip.MustParsePrefix(defaultVPNv4Prefix)
v6p := netip.MustParsePrefix(defaultVPNv6Prefix)
m := &vpnManager{
sessions: make(map[protocol.VPNSessionID]*vpnSession), byIPv4: make(map[netip.Addr]*vpnSession), byIPv6: make(map[netip.Addr]*vpnSession),
maxChunk: maxChunk, maxPackets: maxPackets, pollWait: pollWait, timeout: timeout, tun: tun, mockEcho: mockEcho, allowPrivate: allowPrivate, debug: debug,
maxChunk: maxChunk, maxPackets: maxPackets, maxQueueBytes: maxQueueBytes, pollWait: pollWait, timeout: timeout, batchDelay: batchDelay, tun: tun, mockEcho: mockEcho, allowPrivate: allowPrivate, debug: debug,
v4Prefix: v4p, v6Prefix: v6p,
}
if tun != nil {
@@ -318,7 +408,7 @@ func (m *vpnManager) addOrGet(sid protocol.VPNSessionID, v4, v6 netip.Addr, mtu
if m.byIPv4[v4] != nil || m.byIPv6[v6] != nil {
return nil, errors.New("client VPN address already in use")
}
s := newVPNSession(m, sid, v4, v6, mtu, m.maxChunk, m.maxPackets)
s := newVPNSession(m, sid, v4, v6, mtu, m.maxChunk, m.maxPackets, m.maxQueueBytes, m.batchDelay)
m.sessions[sid] = s
m.byIPv4[v4] = s
m.byIPv6[v6] = s
@@ -376,40 +466,6 @@ func (m *vpnManager) cleanupLoop() {
}
}
func packetAddresses(packet []byte) (src, dst netip.Addr, err error) {
if len(packet) < 1 {
return src, dst, errors.New("empty IP packet")
}
switch packet[0] >> 4 {
case 4:
if len(packet) < 20 {
return src, dst, errors.New("short IPv4 packet")
}
total := int(packet[2])<<8 | int(packet[3])
if total < 20 || total > len(packet) {
return src, dst, errors.New("invalid IPv4 total length")
}
var a, b [4]byte
copy(a[:], packet[12:16])
copy(b[:], packet[16:20])
return netip.AddrFrom4(a), netip.AddrFrom4(b), nil
case 6:
if len(packet) < 40 {
return src, dst, errors.New("short IPv6 packet")
}
total := 40 + (int(packet[4])<<8 | int(packet[5]))
if total > len(packet) {
return src, dst, errors.New("invalid IPv6 payload length")
}
var a, b [16]byte
copy(a[:], packet[8:24])
copy(b[:], packet[24:40])
return netip.AddrFrom16(a), netip.AddrFrom16(b), nil
default:
return src, dst, errors.New("unsupported IP version")
}
}
func destinationAllowed(dst netip.Addr, allowPrivate bool) bool {
if dst.IsUnspecified() || dst.IsMulticast() {
return false
@@ -423,32 +479,68 @@ func destinationAllowed(dst netip.Addr, allowPrivate bool) bool {
return true
}
func (m *vpnManager) acceptClientPacket(s *vpnSession, packet []byte) error {
src, dst, err := packetAddresses(packet)
func (m *vpnManager) dropClientPacket(s *vpnSession, packet []byte, reason string) {
if m.debug != nil {
m.debug.dropped.Add(1)
m.debug.packetf("DROP sid=%s bytes=%d reason=%s", shortSID(s.sid), len(packet), reason)
// A source mismatch can be normal Android link-local/control traffic.
// Never tear down the whole VPN session for one such packet.
m.debug.logf("DROP sid=%s reason=%s", shortSID(s.sid), reason)
}
}
func (m *vpnManager) acceptClientPacket(s *vpnSession, packet []byte) (bool, error) {
src, dst, err := protocol.PacketAddresses(packet)
if err != nil {
return err
m.dropClientPacket(s, packet, err.Error())
return false, nil
}
if src != s.ipv4 && src != s.ipv6 {
return fmt.Errorf("source %s does not match session address", src)
m.dropClientPacket(s, packet, fmt.Sprintf("source %s does not match session address", src))
return false, nil
}
if !destinationAllowed(dst, m.allowPrivate) {
return fmt.Errorf("destination %s is blocked; use --allow-private to permit it", dst)
m.dropClientPacket(s, packet, fmt.Sprintf("destination %s is blocked", dst))
return false, nil
}
if m.mockEcho {
s.enqueue(packet)
return nil
return true, nil
}
if m.tun == nil {
return errors.New("VPN TUN is unavailable")
return false, errors.New("VPN TUN is unavailable")
}
m.tunWriteMu.Lock()
n, err := m.tun.Write(packet)
m.tunWriteMu.Unlock()
if err != nil {
return err
return false, err
}
if n != len(packet) {
return io.ErrShortWrite
return false, io.ErrShortWrite
}
return true, nil
}
func (m *vpnManager) acceptClientTransfer(s *vpnSession, transfer []byte) error {
packets, err := protocol.ParseVPNBatch(transfer)
if err != nil {
// Compatibility with the first packet-VPN build.
if len(transfer) > 0 && (transfer[0]>>4 == 4 || transfer[0]>>4 == 6) {
packets = [][]byte{transfer}
} else {
return err
}
}
for _, packet := range packets {
accepted, err := m.acceptClientPacket(s, packet)
if err != nil {
return err
}
if accepted && m.debug != nil {
m.debug.upPackets.Add(1)
m.debug.upBytes.Add(uint64(len(packet)))
}
}
return nil
}
@@ -467,7 +559,7 @@ func (m *vpnManager) tunReadLoop() {
continue
}
packet := append([]byte(nil), buf[:n]...)
_, dst, e := packetAddresses(packet)
_, dst, e := protocol.PacketAddresses(packet)
if e != nil {
continue
}
@@ -667,8 +759,10 @@ func main() {
port := flag.Int("port", 53, "listen TCP port")
token := flag.String("token", "change-this-token", "shared token")
maxConnections := flag.Int("max-connections", 20000, "maximum simultaneous TCP/53 connections")
maxChunk := flag.Int("chunk-max", 65535, "maximum VPN fragment payload bytes (32-65535)")
maxChunk := flag.Int("chunk-max", protocol.VPNMaxFragment, "maximum DragonTCP transport fragment bytes (32-1048576)")
maxPackets := flag.Int("vpn-buffered-packets", 2048, "maximum queued return IP packets per client")
maxQueueBytes := flag.Int("vpn-buffer-bytes", 8*1024*1024, "maximum queued raw return bytes per client")
batchDelay := flag.Duration("batch-delay", time.Millisecond, "maximum delay to combine adjacent TUN packets into one transfer object")
pollWait := flag.Duration("poll-wait", 100*time.Millisecond, "long-poll wait for a return packet")
sessionTimeout := flag.Duration("session-timeout", 5*time.Minute, "idle VPN session timeout")
tunName := flag.String("tun", "dragontcp0", "Linux TUN interface name")
@@ -684,6 +778,10 @@ func main() {
fmt.Fprintf(os.Stderr, "--chunk-max must be 32-%d\n", protocol.VPNMaxFragment)
os.Exit(2)
}
if *maxPackets < 1 || *maxQueueBytes < protocol.VPNMaxPacket {
fmt.Fprintln(os.Stderr, "invalid VPN buffer limits")
os.Exit(2)
}
if *mtu < 576 || *mtu > 9000 {
fmt.Fprintln(os.Stderr, "--mtu must be 576-9000")
os.Exit(2)
@@ -699,7 +797,7 @@ func main() {
}
defer tun.Close()
}
manager := newVPNManager(tun, *mockEcho, *maxChunk, *maxPackets, *pollWait, *sessionTimeout, *allowPrivate, debug)
manager := newVPNManager(tun, *mockEcho, *maxChunk, *maxPackets, *maxQueueBytes, *pollWait, *sessionTimeout, *batchDelay, *allowPrivate, debug)
addr := net.JoinHostPort(*host, strconv.Itoa(*port))
ln, err := net.Listen("tcp", addr)
if err != nil {
@@ -713,13 +811,13 @@ func main() {
} else {
fmt.Printf("tun=%s mtu=%d IPv4=10.123.0.1/16 IPv6=fd7a:4472:6167:6f6e::1/64 auto_nat=%t\n", *tunName, *mtu, *autoNAT)
}
fmt.Printf("chunk_max=%d poll_wait=%s buffered_packets=%d\n", *maxChunk, pollWait.String(), *maxPackets)
fmt.Printf("chunk_max=%d batch_max=%d batch_delay=%s poll_wait=%s buffered_packets=%d buffer_bytes=%d\n", *maxChunk, protocol.VPNMaxBatch, batchDelay.String(), pollWait.String(), *maxPackets, *maxQueueBytes)
if debug.enabled && *statsEvery > 0 {
go func() {
t := time.NewTicker(*statsEvery)
defer t.Stop()
for range t.C {
fmt.Printf("[DEBUG] STATS uptime=%s conns=%d sessions=%d up_packets=%d down_packets=%d up_bytes=%d down_bytes=%d dropped=%d errors=%d\n", time.Since(debug.started).Round(time.Second), debug.activeConns.Load(), debug.activeSessions.Load(), debug.upPackets.Load(), debug.downPackets.Load(), debug.upBytes.Load(), debug.downBytes.Load(), debug.dropped.Load(), debug.errors.Load())
fmt.Printf("[DEBUG] STATS uptime=%s conns=%d sessions=%d up_packets=%d down_packets=%d up_batches=%d down_batches=%d up_bytes=%d down_bytes=%d dropped=%d errors=%d\n", time.Since(debug.started).Round(time.Second), debug.activeConns.Load(), debug.activeSessions.Load(), debug.upPackets.Load(), debug.downPackets.Load(), debug.upBatches.Load(), debug.downBatches.Load(), debug.upBytes.Load(), debug.downBytes.Load(), debug.dropped.Load(), debug.errors.Load())
}
}()
}