This commit is contained in:
2026-08-16 02:33:07 -03:00
parent 14beee38b0
commit 6dac260155
33 changed files with 2065 additions and 2301 deletions
+211
View File
@@ -0,0 +1,211 @@
package protocol
import (
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"sync"
"time"
)
const (
XORKey byte = 0xAD
// MaxChunkPayload is the hard application-record payload ceiling.
// The adaptive chunk protocol may use any size from 32 bytes through 1 MiB.
MaxChunkPayload = 1024 * 1024
// Framed CPUSH/DATA messages include text metadata in addition to chunk
// bytes, so keep the frame ceiling comfortably above MaxChunkPayload.
MaxHandshake = 2 * 1024 * 1024
)
// 64 KiB balances throughput with memory use at high connection counts.
var BufferPool = sync.Pool{
New: func() any {
b := make([]byte, 64*1024)
return &b
},
}
func ReadRequestFrame(r io.Reader) (uint32, uint32, []byte, error) {
var header [14]byte
if _, err := io.ReadFull(r, header[:]); err != nil {
return 0, 0, nil, err
}
if header[0] != 'U' || header[1] != 'P' {
return 0, 0, nil, errors.New("bad request magic")
}
requestID := binary.BigEndian.Uint32(header[2:6])
reserved := binary.BigEndian.Uint32(header[6:10])
length := binary.BigEndian.Uint32(header[10:14])
if length > MaxHandshake {
return 0, 0, nil, errors.New("handshake payload too large")
}
payload := make([]byte, int(length))
if _, err := io.ReadFull(r, payload); err != nil {
return 0, 0, nil, err
}
XorInPlace(payload)
return requestID, reserved, payload, nil
}
func WriteRequestFrame(w io.Writer, requestID uint32, payload []byte) error {
if len(payload) > MaxHandshake {
return errors.New("request frame payload too large")
}
packet := make([]byte, 14+len(payload))
packet[0], packet[1] = 'U', 'P'
binary.BigEndian.PutUint32(packet[2:6], requestID)
binary.BigEndian.PutUint32(packet[6:10], 0)
binary.BigEndian.PutUint32(packet[10:14], uint32(len(payload)))
copy(packet[14:], payload)
XorInPlace(packet[14:])
return writeAll(w, packet)
}
func ReadResponseFrame(r io.Reader) (uint32, []byte, error) {
var header [10]byte
if _, err := io.ReadFull(r, header[:]); err != nil {
return 0, nil, err
}
if header[0] != 'O' || header[1] != 'K' {
return 0, nil, fmt.Errorf("bad response magic: %q", header[:2])
}
requestID := binary.BigEndian.Uint32(header[2:6])
length := binary.BigEndian.Uint32(header[6:10])
if length > MaxHandshake {
return 0, nil, errors.New("handshake response too large")
}
payload := make([]byte, int(length))
if _, err := io.ReadFull(r, payload); err != nil {
return 0, nil, err
}
XorInPlace(payload)
return requestID, payload, nil
}
func WriteResponseFrame(w io.Writer, requestID uint32, payload []byte) error {
if len(payload) > MaxHandshake {
return errors.New("response frame payload too large")
}
packet := make([]byte, 10+len(payload))
packet[0], packet[1] = 'O', 'K'
binary.BigEndian.PutUint32(packet[2:6], requestID)
binary.BigEndian.PutUint32(packet[6:10], uint32(len(payload)))
copy(packet[10:], payload)
XorInPlace(packet[10:])
return writeAll(w, packet)
}
func writeAll(w io.Writer, b []byte) error {
for len(b) > 0 {
n, err := w.Write(b)
if err != nil {
return err
}
b = b[n:]
}
return nil
}
func CopyXOR(dst net.Conn, src net.Conn) error {
ptr := BufferPool.Get().(*[]byte)
buf := *ptr
defer BufferPool.Put(ptr)
for {
n, err := src.Read(buf)
if n > 0 {
chunk := buf[:n]
XorInPlace(chunk)
if err2 := writeAll(dst, chunk); err2 != nil {
return err2
}
// No restore pass is needed. The next Read overwrites these bytes.
}
if err != nil {
if errors.Is(err, io.EOF) {
return nil
}
return err
}
}
}
func relayPair(a, b net.Conn, copier func(net.Conn, net.Conn) error) {
done := make(chan struct{}, 2)
go func() {
_ = copier(b, a)
if cw, ok := b.(interface{ CloseWrite() error }); ok {
_ = cw.CloseWrite()
}
done <- struct{}{}
}()
go func() {
_ = copier(a, b)
if cw, ok := a.(interface{ CloseWrite() error }); ok {
_ = cw.CloseWrite()
}
done <- struct{}{}
}()
// Preserve normal TCP half-close semantics. The old implementation set a
// 2-second deadline on both connections after the first copy direction
// ended, which truncated slow or large responses. Wait for the remaining
// direction to drain naturally instead.
<-done
<-done
}
func RelayXOR(a, b net.Conn) {
relayPair(a, b, CopyXOR)
}
// RelayRaw allows Go/Linux to use the optimized TCP io.Copy path. On Linux,
// TCP-to-TCP copies can use splice, eliminating the userspace XOR/copy loop.
func RelayRaw(a, b net.Conn) {
relayPair(a, b, func(dst, src net.Conn) error {
_, err := io.Copy(dst, src)
return err
})
}
func TuneTCP(conn net.Conn) {
if tcp, ok := conn.(*net.TCPConn); ok {
_ = tcp.SetNoDelay(true)
_ = tcp.SetKeepAlive(true)
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
}
}
// TuneTCPBuffer optionally requests larger kernel socket buffers. A value <= 0
// leaves Linux/Android autotuning untouched, which is the recommended default
// for large connection counts. For a small number of high-BDP mobile links,
// values such as 1048576 or 4194304 can improve throughput.
func TuneTCPBuffer(conn net.Conn, size int) {
if size <= 0 {
return
}
if tcp, ok := conn.(*net.TCPConn); ok {
_ = tcp.SetReadBuffer(size)
_ = tcp.SetWriteBuffer(size)
}
}
+267
View File
@@ -0,0 +1,267 @@
package protocol
import (
"encoding/binary"
"errors"
"fmt"
"net/netip"
)
const (
VPNCmdOpen byte = 0x30
VPNCmdPush byte = 0x31
VPNCmdPull byte = 0x32
VPNCmdClose byte = 0x33
VPNRespOpened byte = 0x40
VPNRespAck byte = 0x41
VPNRespData byte = 0x42
VPNRespWait byte = 0x43
VPNRespClosed byte = 0x44
VPNRespError byte = 0x7f
VPNNoAck uint32 = 0xffffffff
VPNMaxFragment = 65535
)
type VPNSessionID [16]byte
func VPNError(message string) []byte {
b := []byte(message)
if len(b) > 4096 {
b = b[:4096]
}
out := make([]byte, 1+len(b))
out[0] = VPNRespError
copy(out[1:], b)
return out
}
func ParseVPNError(payload []byte) error {
if len(payload) == 0 {
return errors.New("empty DragonTCP VPN response")
}
if payload[0] == VPNRespError {
return errors.New(string(payload[1:]))
}
return nil
}
// OPEN request:
// cmd(1) sid(16) tokenLen(2) token(N) ipv4(4) ipv6(16) mtu(2)
func BuildVPNOpen(sid VPNSessionID, token string, ipv4, ipv6 netip.Addr, mtu int) ([]byte, error) {
if len(token) > 4096 {
return nil, errors.New("token too long")
}
if !ipv4.Is4() || !ipv6.Is6() {
return nil, errors.New("invalid VPN client addresses")
}
if mtu < 576 || mtu > 65535 {
return nil, errors.New("invalid VPN MTU")
}
out := make([]byte, 1+16+2+len(token)+4+16+2)
out[0] = VPNCmdOpen
copy(out[1:17], sid[:])
binary.BigEndian.PutUint16(out[17:19], uint16(len(token)))
pos := 19
copy(out[pos:pos+len(token)], token)
pos += len(token)
v4 := ipv4.As4()
copy(out[pos:pos+4], v4[:])
pos += 4
v6 := ipv6.As16()
copy(out[pos:pos+16], v6[:])
pos += 16
binary.BigEndian.PutUint16(out[pos:pos+2], uint16(mtu))
return out, nil
}
func ParseVPNOpen(payload []byte) (sid VPNSessionID, token string, ipv4, ipv6 netip.Addr, mtu int, err error) {
if len(payload) < 1+16+2+4+16+2 || payload[0] != VPNCmdOpen {
err = errors.New("bad VPN OPEN")
return
}
copy(sid[:], payload[1:17])
tokenLen := int(binary.BigEndian.Uint16(payload[17:19]))
need := 1 + 16 + 2 + tokenLen + 4 + 16 + 2
if tokenLen < 0 || len(payload) != need {
err = errors.New("bad VPN OPEN length")
return
}
pos := 19
token = string(payload[pos : pos+tokenLen])
pos += tokenLen
var a4 [4]byte
copy(a4[:], payload[pos:pos+4])
ipv4 = netip.AddrFrom4(a4)
pos += 4
var a6 [16]byte
copy(a6[:], payload[pos:pos+16])
ipv6 = netip.AddrFrom16(a6)
pos += 16
mtu = int(binary.BigEndian.Uint16(payload[pos : pos+2]))
return
}
func BuildVPNOpened(maxChunk int) []byte {
if maxChunk > VPNMaxFragment {
maxChunk = VPNMaxFragment
}
if maxChunk < 1 {
maxChunk = 1
}
out := make([]byte, 3)
out[0] = VPNRespOpened
binary.BigEndian.PutUint16(out[1:3], uint16(maxChunk))
return out
}
func ParseVPNOpened(payload []byte) (int, error) {
if err := ParseVPNError(payload); err != nil {
return 0, err
}
if len(payload) != 3 || payload[0] != VPNRespOpened {
return 0, errors.New("bad VPN OPENED response")
}
return int(binary.BigEndian.Uint16(payload[1:3])), nil
}
// PUSH request: cmd(1) sid(16) seq(4) offset(2) total(2) data(N)
func BuildVPNPush(sid VPNSessionID, seq uint32, offset, total int, data []byte) ([]byte, error) {
if total < 1 || total > 65535 || offset < 0 || offset > total || len(data) < 1 || offset+len(data) > total || len(data) > VPNMaxFragment {
return nil, errors.New("invalid VPN PUSH fragment")
}
out := make([]byte, 25+len(data))
out[0] = VPNCmdPush
copy(out[1:17], sid[:])
binary.BigEndian.PutUint32(out[17:21], seq)
binary.BigEndian.PutUint16(out[21:23], uint16(offset))
binary.BigEndian.PutUint16(out[23:25], uint16(total))
copy(out[25:], data)
return out, nil
}
func ParseVPNPush(payload []byte) (sid VPNSessionID, seq uint32, offset, total int, data []byte, err error) {
if len(payload) < 26 || payload[0] != VPNCmdPush {
err = errors.New("bad VPN PUSH")
return
}
copy(sid[:], payload[1:17])
seq = binary.BigEndian.Uint32(payload[17:21])
offset = int(binary.BigEndian.Uint16(payload[21:23]))
total = int(binary.BigEndian.Uint16(payload[23:25]))
data = payload[25:]
if total < 1 || offset < 0 || offset > total || len(data) < 1 || offset+len(data) > total {
err = errors.New("bad VPN PUSH fragment bounds")
}
return
}
func BuildVPNAck(seq uint32, accepted int) []byte {
out := make([]byte, 7)
out[0] = VPNRespAck
binary.BigEndian.PutUint32(out[1:5], seq)
binary.BigEndian.PutUint16(out[5:7], uint16(accepted))
return out
}
func ParseVPNAck(payload []byte) (seq uint32, accepted int, err error) {
if e := ParseVPNError(payload); e != nil {
err = e
return
}
if len(payload) != 7 || payload[0] != VPNRespAck {
err = errors.New("bad VPN ACK")
return
}
seq = binary.BigEndian.Uint32(payload[1:5])
accepted = int(binary.BigEndian.Uint16(payload[5:7]))
return
}
// PULL request: cmd(1) sid(16) ack(4) want(4) offset(2) limit(2)
func BuildVPNPull(sid VPNSessionID, ack, want uint32, offset, limit int) ([]byte, error) {
if offset < 0 || offset > 65535 || limit < 1 || limit > VPNMaxFragment {
return nil, errors.New("invalid VPN PULL")
}
out := make([]byte, 29)
out[0] = VPNCmdPull
copy(out[1:17], sid[:])
binary.BigEndian.PutUint32(out[17:21], ack)
binary.BigEndian.PutUint32(out[21:25], want)
binary.BigEndian.PutUint16(out[25:27], uint16(offset))
binary.BigEndian.PutUint16(out[27:29], uint16(limit))
return out, nil
}
func ParseVPNPull(payload []byte) (sid VPNSessionID, ack, want uint32, offset, limit int, err error) {
if len(payload) != 29 || payload[0] != VPNCmdPull {
err = errors.New("bad VPN PULL")
return
}
copy(sid[:], payload[1:17])
ack = binary.BigEndian.Uint32(payload[17:21])
want = binary.BigEndian.Uint32(payload[21:25])
offset = int(binary.BigEndian.Uint16(payload[25:27]))
limit = int(binary.BigEndian.Uint16(payload[27:29]))
if limit < 1 {
err = errors.New("bad VPN PULL limit")
}
return
}
// DATA response: cmd(1) seq(4) offset(2) total(2) data(N)
func BuildVPNData(seq uint32, offset, total int, data []byte) []byte {
out := make([]byte, 9+len(data))
out[0] = VPNRespData
binary.BigEndian.PutUint32(out[1:5], seq)
binary.BigEndian.PutUint16(out[5:7], uint16(offset))
binary.BigEndian.PutUint16(out[7:9], uint16(total))
copy(out[9:], data)
return out
}
func ParseVPNData(payload []byte) (seq uint32, offset, total int, data []byte, wait bool, err error) {
if e := ParseVPNError(payload); e != nil {
err = e
return
}
if len(payload) == 1 && payload[0] == VPNRespWait {
wait = true
return
}
if len(payload) < 10 || payload[0] != VPNRespData {
err = fmt.Errorf("bad VPN DATA response type/length")
return
}
seq = binary.BigEndian.Uint32(payload[1:5])
offset = int(binary.BigEndian.Uint16(payload[5:7]))
total = int(binary.BigEndian.Uint16(payload[7:9]))
data = payload[9:]
if total < 1 || offset < 0 || offset+len(data) > total || len(data) < 1 {
err = errors.New("bad VPN DATA bounds")
}
return
}
func BuildVPNClose(sid VPNSessionID) []byte {
out := make([]byte, 17)
out[0] = VPNCmdClose
copy(out[1:17], sid[:])
return out
}
func ParseVPNClose(payload []byte) (sid VPNSessionID, err error) {
if len(payload) != 17 || payload[0] != VPNCmdClose {
return sid, errors.New("bad VPN CLOSE")
}
copy(sid[:], payload[1:17])
return sid, nil
}
func IsVPNCommand(payload []byte) bool {
if len(payload) == 0 {
return false
}
return payload[0] >= VPNCmdOpen && payload[0] <= VPNCmdClose
}
+44
View File
@@ -0,0 +1,44 @@
//go:build arm || 386
package protocol
import "unsafe"
const xorWordMask32 uint32 = 0xADADADAD
// XorInPlace is the 32-bit optimized path used by ARMv7/386 builds.
// It aligns once, then processes 32 bytes per iteration with native uint32
// operations instead of a byte-at-a-time loop.
func XorInPlace(b []byte) {
n := len(b)
if n == 0 {
return
}
i := 0
for i < n && (uintptr(unsafe.Pointer(&b[i]))&3) != 0 {
b[i] ^= XORKey
i++
}
for ; i+32 <= n; i += 32 {
p := unsafe.Pointer(&b[i])
*(*uint32)(unsafe.Add(p, 0)) ^= xorWordMask32
*(*uint32)(unsafe.Add(p, 4)) ^= xorWordMask32
*(*uint32)(unsafe.Add(p, 8)) ^= xorWordMask32
*(*uint32)(unsafe.Add(p, 12)) ^= xorWordMask32
*(*uint32)(unsafe.Add(p, 16)) ^= xorWordMask32
*(*uint32)(unsafe.Add(p, 20)) ^= xorWordMask32
*(*uint32)(unsafe.Add(p, 24)) ^= xorWordMask32
*(*uint32)(unsafe.Add(p, 28)) ^= xorWordMask32
}
for ; i+4 <= n; i += 4 {
p := (*uint32)(unsafe.Pointer(&b[i]))
*p ^= xorWordMask32
}
for ; i < n; i++ {
b[i] ^= XORKey
}
}
+50
View File
@@ -0,0 +1,50 @@
//go:build amd64 || arm64
package protocol
import "unsafe"
const xorWordMask uint64 = 0xADADADADADADADAD
// XorInPlace is optimized for 64-bit targets (amd64/arm64).
//
// It aligns the input once, then XORs 64 bytes per loop iteration using
// eight native 64-bit operations. This removes the encoding/binary call
// overhead from the hot relay path and lets the compiler generate a tight
// load/xor/store loop.
func XorInPlace(b []byte) {
n := len(b)
if n == 0 {
return
}
i := 0
// Align the pointer for native uint64 accesses. This is normally already
// aligned for pooled relay buffers, but also makes this safe for subslices.
for i < n && (uintptr(unsafe.Pointer(&b[i]))&7) != 0 {
b[i] ^= XORKey
i++
}
for ; i+64 <= n; i += 64 {
p := unsafe.Pointer(&b[i])
*(*uint64)(unsafe.Add(p, 0)) ^= xorWordMask
*(*uint64)(unsafe.Add(p, 8)) ^= xorWordMask
*(*uint64)(unsafe.Add(p, 16)) ^= xorWordMask
*(*uint64)(unsafe.Add(p, 24)) ^= xorWordMask
*(*uint64)(unsafe.Add(p, 32)) ^= xorWordMask
*(*uint64)(unsafe.Add(p, 40)) ^= xorWordMask
*(*uint64)(unsafe.Add(p, 48)) ^= xorWordMask
*(*uint64)(unsafe.Add(p, 56)) ^= xorWordMask
}
for ; i+8 <= n; i += 8 {
p := (*uint64)(unsafe.Pointer(&b[i]))
*p ^= xorWordMask
}
for ; i < n; i++ {
b[i] ^= XORKey
}
}
+10
View File
@@ -0,0 +1,10 @@
//go:build !amd64 && !arm64 && !arm && !386
package protocol
// Generic fallback for 32-bit and uncommon architectures.
func XorInPlace(b []byte) {
for i := range b {
b[i] ^= XORKey
}
}