package main

import (
	"bytes"
	"crypto/subtle"
	"encoding/hex"
	"errors"
	"flag"
	"fmt"
	"io"
	"net"
	"net/netip"
	"os"
	"os/exec"
	"os/signal"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"syscall"
	"time"
	"unsafe"

	"dragontcpvpn/internal/protocol"
)

const (
	defaultVPNv4Prefix = "10.123.0.0/16"
	defaultVPNv6Prefix = "fd7a:4472:6167:6f6e::/64"
)

type debugStats struct {
	enabled        bool
	packets        bool
	started        time.Time
	activeConns    atomic.Int64
	activeSessions atomic.Int64
	upPackets      atomic.Uint64
	downPackets    atomic.Uint64
	upBatches      atomic.Uint64
	downBatches    atomic.Uint64
	upBytes        atomic.Uint64
	downBytes      atomic.Uint64
	dropped        atomic.Uint64
	errors         atomic.Uint64
}

func (d *debugStats) logf(format string, args ...any) {
	if d != nil && d.enabled {
		fmt.Printf("[DEBUG] "+format+"\n", args...)
	}
}
func (d *debugStats) packetf(format string, args ...any) {
	if d != nil && d.packets {
		fmt.Printf("[PACKET] "+format+"\n", args...)
	}
}
func (d *debugStats) errorf(format string, args ...any) {
	if d != nil {
		d.errors.Add(1)
		if d.enabled {
			fmt.Printf("[ERROR] "+format+"\n", args...)
		}
	}
}

func tokenEqual(a, b string) bool {
	if len(a) != len(b) {
		return false
	}
	return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}

type vpnSession struct {
	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]*downTransfer
	nextDown          uint32
	closed            bool
	lastSeen          time.Time
	pendingPackets    [][]byte
	pendingEncoded    int
	pendingTimer      *time.Timer
	queuedPacketCount int
	queuedBytes       int

	upMu              sync.Mutex
	expectedUp        uint32
	currentSeq        uint32
	currentTotal      int
	currentBuf        []byte
	haveCurrent       bool
	lastComplete      uint32
	lastCompleteTotal int
	haveLastComplete  bool
}

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, maxQueueBytes: maxQueueBytes, batchDelay: batchDelay,
		manager: m, notify: make(chan struct{}), packets: make(map[uint32]*downTransfer, maxPackets), lastSeen: time.Now(),
	}
}

func (s *vpnSession) signalLocked() {
	close(s.notify)
	s.notify = make(chan struct{})
}
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) > protocol.VPNMaxPacket {
		return false
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.closed {
		return false
	}
	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
	}
	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()
	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 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
}

func (s *vpnSession) push(seq uint32, offset, total int, data []byte) (int, error) {
	s.upMu.Lock()
	defer s.upMu.Unlock()
	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 {
		s.touch()
		return s.lastCompleteTotal, nil
	}
	if seq < s.expectedUp {
		return 0, fmt.Errorf("old upload sequence %d", seq)
	}
	if seq > s.expectedUp {
		return 0, fmt.Errorf("upload sequence %d expected %d", seq, s.expectedUp)
	}

	if !s.haveCurrent {
		if offset != 0 {
			return 0, errors.New("first fragment offset must be zero")
		}
		s.haveCurrent = true
		s.currentSeq = seq
		s.currentTotal = total
		s.currentBuf = make([]byte, 0, total)
	}
	if s.currentSeq != seq || s.currentTotal != total {
		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) && bytes.Equal(s.currentBuf[offset:end], data) {
			return len(s.currentBuf), nil
		}
		return 0, errors.New("retry fragment does not match accepted data")
	}
	if offset != len(s.currentBuf) {
		return 0, fmt.Errorf("fragment offset %d expected %d", offset, len(s.currentBuf))
	}

	s.currentBuf = append(s.currentBuf, data...)
	accepted := len(s.currentBuf)
	if accepted < total {
		s.touch()
		return accepted, nil
	}

	transfer := append([]byte(nil), s.currentBuf...)
	s.haveCurrent = false
	s.currentBuf = nil

	if err := s.manager.acceptClientTransfer(s, transfer); err != nil {
		return 0, err
	}

	s.lastComplete = seq
	s.lastCompleteTotal = total
	s.haveLastComplete = true
	s.expectedUp++
	s.touch()
	if s.manager.debug != nil {
		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
}

func (s *vpnSession) pull(ack, want uint32, offset, limit int, wait time.Duration) ([]byte, int, bool, error) {
	if offset < 0 || limit < 1 || limit > s.maxChunk {
		return nil, 0, false, errors.New("invalid pull")
	}
	timer := time.NewTimer(wait)
	defer timer.Stop()
	for {
		s.mu.Lock()
		s.touchLocked()
		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 rec, ok := s.packets[want]; ok {
			if offset >= len(rec.data) {
				s.mu.Unlock()
				return nil, len(rec.data), false, errors.New("pull offset beyond transfer")
			}
			end := offset + limit
			if end > len(rec.data) {
				end = len(rec.data)
			}
			out := append([]byte(nil), rec.data[offset:end]...)
			total := len(rec.data)
			s.mu.Unlock()
			return out, total, false, nil
		}
		if s.closed {
			s.mu.Unlock()
			return nil, 0, false, net.ErrClosed
		}
		ch := s.notify
		s.mu.Unlock()
		select {
		case <-ch:
		case <-timer.C:
			return nil, 0, true, nil
		}
	}
}

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
	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, 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, maxQueueBytes: maxQueueBytes, pollWait: pollWait, timeout: timeout, batchDelay: batchDelay, tun: tun, mockEcho: mockEcho, allowPrivate: allowPrivate, debug: debug,
		v4Prefix: v4p, v6Prefix: v6p,
	}
	if tun != nil {
		go m.tunReadLoop()
	}
	go m.cleanupLoop()
	return m
}

func (m *vpnManager) addOrGet(sid protocol.VPNSessionID, v4, v6 netip.Addr, mtu int) (*vpnSession, error) {
	if !m.v4Prefix.Contains(v4) || v4 == netip.MustParseAddr("10.123.0.1") {
		return nil, errors.New("client IPv4 outside DragonTCP subnet")
	}
	if !m.v6Prefix.Contains(v6) || v6 == netip.MustParseAddr("fd7a:4472:6167:6f6e::1") {
		return nil, errors.New("client IPv6 outside DragonTCP subnet")
	}
	if mtu < 576 || mtu > 9000 {
		return nil, errors.New("invalid client MTU")
	}

	m.mu.Lock()
	defer m.mu.Unlock()
	if old := m.sessions[sid]; old != nil {
		if old.ipv4 != v4 || old.ipv6 != v6 {
			return nil, errors.New("session address mismatch")
		}
		old.touch()
		return old, nil
	}
	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, m.maxQueueBytes, m.batchDelay)
	m.sessions[sid] = s
	m.byIPv4[v4] = s
	m.byIPv6[v6] = s
	if m.debug != nil {
		m.debug.activeSessions.Add(1)
		m.debug.logf("SESSION OPEN sid=%s ipv4=%s ipv6=%s mtu=%d", shortSID(sid), v4, v6, mtu)
	}
	return s, nil
}

func (m *vpnManager) get(sid protocol.VPNSessionID) *vpnSession {
	m.mu.RLock()
	s := m.sessions[sid]
	m.mu.RUnlock()
	return s
}
func (m *vpnManager) remove(sid protocol.VPNSessionID) {
	m.mu.Lock()
	s := m.sessions[sid]
	if s != nil {
		delete(m.sessions, sid)
		delete(m.byIPv4, s.ipv4)
		delete(m.byIPv6, s.ipv6)
	}
	m.mu.Unlock()
	if s != nil {
		s.close()
		if m.debug != nil {
			m.debug.activeSessions.Add(-1)
			m.debug.logf("SESSION CLOSE sid=%s", shortSID(sid))
		}
	}
}

func (m *vpnManager) cleanupLoop() {
	ticker := time.NewTicker(30 * time.Second)
	defer ticker.Stop()
	for range ticker.C {
		cutoff := time.Now().Add(-m.timeout)
		var stale []protocol.VPNSessionID
		m.mu.RLock()
		for sid, s := range m.sessions {
			s.mu.Lock()
			last := s.lastSeen
			closed := s.closed
			s.mu.Unlock()
			if closed || last.Before(cutoff) {
				stale = append(stale, sid)
			}
		}
		m.mu.RUnlock()
		for _, sid := range stale {
			m.remove(sid)
		}
	}
}

func destinationAllowed(dst netip.Addr, allowPrivate bool) bool {
	if dst.IsUnspecified() || dst.IsMulticast() {
		return false
	}
	if allowPrivate {
		return true
	}
	if dst.IsLoopback() || dst.IsLinkLocalUnicast() || dst.IsPrivate() {
		return false
	}
	return true
}

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 {
		m.dropClientPacket(s, packet, err.Error())
		return false, nil
	}
	if src != s.ipv4 && src != s.ipv6 {
		m.dropClientPacket(s, packet, fmt.Sprintf("source %s does not match session address", src))
		return false, nil
	}
	if !destinationAllowed(dst, m.allowPrivate) {
		m.dropClientPacket(s, packet, fmt.Sprintf("destination %s is blocked", dst))
		return false, nil
	}
	if m.mockEcho {
		s.enqueue(packet)
		return true, nil
	}
	if m.tun == nil {
		return false, errors.New("VPN TUN is unavailable")
	}
	m.tunWriteMu.Lock()
	n, err := m.tun.Write(packet)
	m.tunWriteMu.Unlock()
	if err != nil {
		return false, err
	}
	if n != len(packet) {
		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
}

func (m *vpnManager) tunReadLoop() {
	buf := make([]byte, 65535)
	for {
		n, err := m.tun.Read(buf)
		if err != nil {
			if m.debug != nil {
				m.debug.errorf("TUN read: %v", err)
			}
			return
		}
		if n < 1 {
			continue
		}
		packet := append([]byte(nil), buf[:n]...)
		_, dst, e := protocol.PacketAddresses(packet)
		if e != nil {
			continue
		}
		m.mu.RLock()
		var s *vpnSession
		if dst.Is4() {
			s = m.byIPv4[dst]
		} else {
			s = m.byIPv6[dst]
		}
		m.mu.RUnlock()
		if s != nil {
			s.enqueue(packet)
		}
	}
}

func shortSID(sid protocol.VPNSessionID) string { return hex.EncodeToString(sid[:4]) }

func processVPN(conn net.Conn, requestID uint32, payload []byte, token string, m *vpnManager) error {
	switch payload[0] {
	case protocol.VPNCmdOpen:
		sid, tok, v4, v6, mtu, err := protocol.ParseVPNOpen(payload)
		if err != nil {
			return protocol.WriteResponseFrame(conn, requestID, protocol.VPNError(err.Error()))
		}
		if !tokenEqual(tok, token) {
			return protocol.WriteResponseFrame(conn, requestID, protocol.VPNError("authentication failed"))
		}
		_, err = m.addOrGet(sid, v4, v6, mtu)
		if err != nil {
			return protocol.WriteResponseFrame(conn, requestID, protocol.VPNError(err.Error()))
		}
		return protocol.WriteResponseFrame(conn, requestID, protocol.BuildVPNOpened(m.maxChunk))
	case protocol.VPNCmdPush:
		sid, seq, offset, total, data, err := protocol.ParseVPNPush(payload)
		if err != nil {
			return protocol.WriteResponseFrame(conn, requestID, protocol.VPNError(err.Error()))
		}
		s := m.get(sid)
		if s == nil {
			return protocol.WriteResponseFrame(conn, requestID, protocol.VPNError("unknown VPN session"))
		}
		accepted, err := s.push(seq, offset, total, data)
		if err != nil {
			return protocol.WriteResponseFrame(conn, requestID, protocol.VPNError(err.Error()))
		}
		return protocol.WriteResponseFrame(conn, requestID, protocol.BuildVPNAck(seq, accepted))
	case protocol.VPNCmdPull:
		sid, ack, want, offset, limit, err := protocol.ParseVPNPull(payload)
		if err != nil {
			return protocol.WriteResponseFrame(conn, requestID, protocol.VPNError(err.Error()))
		}
		s := m.get(sid)
		if s == nil {
			return protocol.WriteResponseFrame(conn, requestID, protocol.VPNError("unknown VPN session"))
		}
		if limit > s.maxChunk {
			limit = s.maxChunk
		}
		data, total, wait, err := s.pull(ack, want, offset, limit, m.pollWait)
		if err != nil {
			return protocol.WriteResponseFrame(conn, requestID, protocol.VPNError(err.Error()))
		}
		if wait {
			return protocol.WriteResponseFrame(conn, requestID, []byte{protocol.VPNRespWait})
		}
		m.debug.packetf("DOWN sid=%s seq=%d offset=%d bytes=%d total=%d", shortSID(sid), want, offset, len(data), total)
		return protocol.WriteResponseFrame(conn, requestID, protocol.BuildVPNData(want, offset, total, data))
	case protocol.VPNCmdClose:
		sid, err := protocol.ParseVPNClose(payload)
		if err != nil {
			return protocol.WriteResponseFrame(conn, requestID, protocol.VPNError(err.Error()))
		}
		m.remove(sid)
		return protocol.WriteResponseFrame(conn, requestID, []byte{protocol.VPNRespClosed})
	default:
		return protocol.WriteResponseFrame(conn, requestID, protocol.VPNError("unknown VPN command"))
	}
}

func handleConn(conn net.Conn, token string, m *vpnManager, slots chan struct{}, debug *debugStats) {
	defer func() { <-slots; debug.activeConns.Add(-1); _ = conn.Close() }()
	protocol.TuneTCP(conn)
	for {
		_ = conn.SetDeadline(time.Now().Add(30 * time.Second))
		requestID, _, payload, err := protocol.ReadRequestFrame(conn)
		if err != nil {
			if !errors.Is(err, io.EOF) && !errors.Is(err, net.ErrClosed) {
				debug.errorf("peer=%v read: %v", conn.RemoteAddr(), err)
			}
			return
		}
		if !protocol.IsVPNCommand(payload) {
			_ = protocol.WriteResponseFrame(conn, requestID, protocol.VPNError("this binary accepts DragonTCP VPN packet commands only"))
			continue
		}
		if err := processVPN(conn, requestID, payload, token, m); err != nil {
			return
		}
	}
}

// Linux TUN setup.
type ifreq struct {
	Name  [16]byte
	Flags uint16
	_     [22]byte
}

const tunSetIFF = 0x400454ca
const iffTun = 0x0001
const iffNoPI = 0x1000

func openTun(name string) (*os.File, error) {
	fd, err := syscall.Open("/dev/net/tun", syscall.O_RDWR|syscall.O_CLOEXEC, 0)
	if err != nil {
		return nil, err
	}
	var req ifreq
	copy(req.Name[:], []byte(name))
	req.Flags = iffTun | iffNoPI
	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(tunSetIFF), uintptr(unsafe.Pointer(&req)))
	if errno != 0 {
		syscall.Close(fd)
		return nil, errno
	}
	return os.NewFile(uintptr(fd), name), nil
}

func run(cmd string, args ...string) error {
	c := exec.Command(cmd, args...)
	out, err := c.CombinedOutput()
	if err != nil {
		return fmt.Errorf("%s %s: %v: %s", cmd, strings.Join(args, " "), err, strings.TrimSpace(string(out)))
	}
	return nil
}
func runOptional(debug *debugStats, cmd string, args ...string) {
	if err := run(cmd, args...); err != nil {
		debug.logf("optional command failed: %v", err)
	}
}
func ensureRule(debug *debugStats, binary string, argsCheck, argsAdd []string) {
	if err := exec.Command(binary, argsCheck...).Run(); err == nil {
		return
	}
	if err := run(binary, argsAdd...); err != nil {
		debug.logf("NAT rule warning: %v", err)
	}
}
func setupLinuxVPN(tunName string, mtu int, autoNAT bool, debug *debugStats) (*os.File, error) {
	tun, err := openTun(tunName)
	if err != nil {
		return nil, fmt.Errorf("open /dev/net/tun: %w", err)
	}
	fail := func(e error) (*os.File, error) { tun.Close(); return nil, e }
	if err := run("ip", "link", "set", "dev", tunName, "mtu", strconv.Itoa(mtu)); err != nil {
		return fail(err)
	}
	if err := run("ip", "addr", "replace", "10.123.0.1/16", "dev", tunName); err != nil {
		return fail(err)
	}
	// IPv6 may be disabled on some hosts; report clearly instead of silently bypassing it.
	if err := run("ip", "-6", "addr", "replace", "fd7a:4472:6167:6f6e::1/64", "dev", tunName); err != nil {
		return fail(err)
	}
	if err := run("ip", "link", "set", "dev", tunName, "up"); err != nil {
		return fail(err)
	}
	if err := os.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte("1\n"), 0644); err != nil {
		return fail(fmt.Errorf("enable IPv4 forwarding: %w", err))
	}
	if err := os.WriteFile("/proc/sys/net/ipv6/conf/all/forwarding", []byte("1\n"), 0644); err != nil {
		return fail(fmt.Errorf("enable IPv6 forwarding: %w", err))
	}
	if autoNAT {
		if _, err := exec.LookPath("iptables"); err != nil {
			return fail(errors.New("iptables not found; install iptables or start with --auto-nat=false and configure NAT yourself"))
		}
		ensureRule(debug, "iptables", []string{"-t", "nat", "-C", "POSTROUTING", "-s", "10.123.0.0/16", "-j", "MASQUERADE"}, []string{"-t", "nat", "-A", "POSTROUTING", "-s", "10.123.0.0/16", "-j", "MASQUERADE"})
		ensureRule(debug, "iptables", []string{"-C", "FORWARD", "-i", tunName, "-j", "ACCEPT"}, []string{"-A", "FORWARD", "-i", tunName, "-j", "ACCEPT"})
		ensureRule(debug, "iptables", []string{"-C", "FORWARD", "-o", tunName, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}, []string{"-A", "FORWARD", "-o", tunName, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"})
		if _, err := exec.LookPath("ip6tables"); err == nil {
			ensureRule(debug, "ip6tables", []string{"-t", "nat", "-C", "POSTROUTING", "-s", "fd7a:4472:6167:6f6e::/64", "-j", "MASQUERADE"}, []string{"-t", "nat", "-A", "POSTROUTING", "-s", "fd7a:4472:6167:6f6e::/64", "-j", "MASQUERADE"})
			ensureRule(debug, "ip6tables", []string{"-C", "FORWARD", "-i", tunName, "-j", "ACCEPT"}, []string{"-A", "FORWARD", "-i", tunName, "-j", "ACCEPT"})
			ensureRule(debug, "ip6tables", []string{"-C", "FORWARD", "-o", tunName, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}, []string{"-A", "FORWARD", "-o", tunName, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"})
		} else {
			debug.logf("WARNING: ip6tables not found; IPv6 Internet access needs manual routing/NAT")
		}
	}
	return tun, nil
}

func main() {
	host := flag.String("host", "0.0.0.0", "listen host")
	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", 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")
	mtu := flag.Int("mtu", 1280, "server TUN MTU")
	autoNAT := flag.Bool("auto-nat", true, "configure IPv4/IPv6 forwarding and iptables MASQUERADE")
	allowPrivate := flag.Bool("allow-private", false, "allow VPN clients to access private/link-local destinations")
	mockEcho := flag.Bool("mock-echo", false, "test mode: echo client IP packets back instead of using Linux TUN/NAT")
	debugOn := flag.Bool("debug", false, "debug sessions and statistics")
	debugPackets := flag.Bool("debug-packets", false, "very verbose per-IP-packet logging")
	statsEvery := flag.Duration("debug-stats-interval", 10*time.Second, "debug statistics interval; 0 disables")
	flag.Parse()
	if *maxChunk < 32 || *maxChunk > protocol.VPNMaxFragment {
		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)
	}
	debug := &debugStats{enabled: *debugOn, packets: *debugPackets, started: time.Now()}
	var tun *os.File
	var err error
	if !*mockEcho {
		tun, err = setupLinuxVPN(*tunName, *mtu, *autoNAT, debug)
		if err != nil {
			fmt.Fprintln(os.Stderr, "VPN setup failed:", err)
			os.Exit(1)
		}
		defer tun.Close()
	}
	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 {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	defer ln.Close()
	fmt.Printf("DragonTCP VPN server listening on %s\n", addr)
	if *mockEcho {
		fmt.Println("mode=mock-echo (no Internet forwarding)")
	} 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 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_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())
			}
		}()
	}
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
	go func() { <-sig; fmt.Println("Stopping DragonTCP VPN server..."); ln.Close() }()
	slots := make(chan struct{}, *maxConnections)
	for {
		conn, err := ln.Accept()
		if err != nil {
			break
		}
		select {
		case slots <- struct{}{}:
			debug.activeConns.Add(1)
			go handleConn(conn, *token, manager, slots, debug)
		default:
			_ = conn.Close()
		}
	}
}
