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 // Raw IP packets remain bounded by the IPv4/IPv6 packet-length model. VPNMaxPacket = 65535 // DragonTCP transfer objects/records are independent of IP packet size. // Multiple IP packets may be batched into one transfer object. VPNMaxFragment = 1024 * 1024 VPNMaxBatch = 1024 * 1024 VPNBatchVersion byte = 1 ) 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 > VPNMaxPacket { 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 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 } // OPENED v2 response: cmd(1) maxChunk(4). // ParseVPNOpened also accepts the old 3-byte/uint16 response for compatibility. func BuildVPNOpened(maxChunk int) []byte { if maxChunk > VPNMaxFragment { maxChunk = VPNMaxFragment } if maxChunk < 1 { maxChunk = 1 } out := make([]byte, 5) out[0] = VPNRespOpened binary.BigEndian.PutUint32(out[1:5], uint32(maxChunk)) return out } func ParseVPNOpened(payload []byte) (int, error) { if err := ParseVPNError(payload); err != nil { return 0, err } if len(payload) == 5 && payload[0] == VPNRespOpened { v := int(binary.BigEndian.Uint32(payload[1:5])) if v < 1 || v > VPNMaxFragment { return 0, errors.New("bad VPN OPENED max chunk") } return v, nil } if len(payload) == 3 && payload[0] == VPNRespOpened { return int(binary.BigEndian.Uint16(payload[1:3])), nil } return 0, errors.New("bad VPN OPENED response") } // PUSH v2 request: cmd(1) sid(16) seq(4) offset(4) total(4) data(N) func BuildVPNPush(sid VPNSessionID, seq uint32, offset, total int, data []byte) ([]byte, error) { if total < 1 || total > VPNMaxBatch || 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, 29+len(data)) out[0] = VPNCmdPush copy(out[1:17], sid[:]) binary.BigEndian.PutUint32(out[17:21], seq) binary.BigEndian.PutUint32(out[21:25], uint32(offset)) binary.BigEndian.PutUint32(out[25:29], uint32(total)) copy(out[29:], data) return out, nil } func ParseVPNPush(payload []byte) (sid VPNSessionID, seq uint32, offset, total int, data []byte, err error) { if len(payload) < 30 || 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.Uint32(payload[21:25])) total = int(binary.BigEndian.Uint32(payload[25:29])) data = payload[29:] if total < 1 || total > VPNMaxBatch || offset < 0 || offset > total || len(data) < 1 || len(data) > VPNMaxFragment || offset+len(data) > total { err = errors.New("bad VPN PUSH fragment bounds") } return } func BuildVPNAck(seq uint32, accepted int) []byte { out := make([]byte, 9) out[0] = VPNRespAck binary.BigEndian.PutUint32(out[1:5], seq) binary.BigEndian.PutUint32(out[5:9], uint32(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) != 9 || payload[0] != VPNRespAck { err = errors.New("bad VPN ACK") return } seq = binary.BigEndian.Uint32(payload[1:5]) accepted = int(binary.BigEndian.Uint32(payload[5:9])) return } // PULL v2 request: cmd(1) sid(16) ack(4) want(4) offset(4) limit(4) func BuildVPNPull(sid VPNSessionID, ack, want uint32, offset, limit int) ([]byte, error) { if offset < 0 || offset > VPNMaxBatch || limit < 1 || limit > VPNMaxFragment { return nil, errors.New("invalid VPN PULL") } out := make([]byte, 33) out[0] = VPNCmdPull copy(out[1:17], sid[:]) binary.BigEndian.PutUint32(out[17:21], ack) binary.BigEndian.PutUint32(out[21:25], want) binary.BigEndian.PutUint32(out[25:29], uint32(offset)) binary.BigEndian.PutUint32(out[29:33], uint32(limit)) return out, nil } func ParseVPNPull(payload []byte) (sid VPNSessionID, ack, want uint32, offset, limit int, err error) { if len(payload) != 33 || 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.Uint32(payload[25:29])) limit = int(binary.BigEndian.Uint32(payload[29:33])) if offset < 0 || offset > VPNMaxBatch || limit < 1 || limit > VPNMaxFragment { err = errors.New("bad VPN PULL bounds") } return } // DATA v2 response: cmd(1) seq(4) offset(4) total(4) data(N) func BuildVPNData(seq uint32, offset, total int, data []byte) []byte { out := make([]byte, 13+len(data)) out[0] = VPNRespData binary.BigEndian.PutUint32(out[1:5], seq) binary.BigEndian.PutUint32(out[5:9], uint32(offset)) binary.BigEndian.PutUint32(out[9:13], uint32(total)) copy(out[13:], 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) < 14 || payload[0] != VPNRespData { err = fmt.Errorf("bad VPN DATA response type/length") return } seq = binary.BigEndian.Uint32(payload[1:5]) offset = int(binary.BigEndian.Uint32(payload[5:9])) total = int(binary.BigEndian.Uint32(payload[9:13])) data = payload[13:] if total < 1 || total > VPNMaxBatch || offset < 0 || offset+len(data) > total || len(data) < 1 || len(data) > VPNMaxFragment { err = errors.New("bad VPN DATA bounds") } return } // A transfer object is a batch of raw IP packets: // version(1), then repeated packetLen(2) + packet bytes. func BuildVPNBatch(packets [][]byte) ([]byte, error) { if len(packets) == 0 { return nil, errors.New("empty VPN batch") } total := 1 for _, packet := range packets { if len(packet) < 1 || len(packet) > VPNMaxPacket { return nil, errors.New("invalid IP packet length in VPN batch") } total += 2 + len(packet) if total > VPNMaxBatch { return nil, errors.New("VPN batch exceeds maximum") } } out := make([]byte, total) out[0] = VPNBatchVersion pos := 1 for _, packet := range packets { binary.BigEndian.PutUint16(out[pos:pos+2], uint16(len(packet))) pos += 2 copy(out[pos:pos+len(packet)], packet) pos += len(packet) } return out, nil } func ParseVPNBatch(batch []byte) ([][]byte, error) { if len(batch) < 4 || len(batch) > VPNMaxBatch || batch[0] != VPNBatchVersion { return nil, errors.New("bad VPN batch") } packets := make([][]byte, 0, 8) pos := 1 for pos < len(batch) { if pos+2 > len(batch) { return nil, errors.New("truncated VPN batch packet length") } n := int(binary.BigEndian.Uint16(batch[pos : pos+2])) pos += 2 if n < 1 || n > VPNMaxPacket || pos+n > len(batch) { return nil, errors.New("invalid VPN batch packet") } packet := make([]byte, n) copy(packet, batch[pos:pos+n]) packets = append(packets, packet) pos += n } if len(packets) == 0 { return nil, errors.New("VPN batch contains no packets") } return packets, nil } // PacketAddresses returns the source and destination addresses from a raw // IPv4/IPv6 packet. The packet may contain trailing bytes; the IP header's own // length field is validated against the supplied buffer. 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 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 }