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
+153 -44
View File
@@ -20,8 +20,17 @@ const (
VPNRespClosed byte = 0x44
VPNRespError byte = 0x7f
VPNNoAck uint32 = 0xffffffff
VPNMaxFragment = 65535
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
@@ -56,7 +65,7 @@ func BuildVPNOpen(sid VPNSessionID, token string, ipv4, ipv6 netip.Addr, mtu int
if !ipv4.Is4() || !ipv6.Is6() {
return nil, errors.New("invalid VPN client addresses")
}
if mtu < 576 || mtu > 65535 {
if mtu < 576 || mtu > VPNMaxPacket {
return nil, errors.New("invalid VPN MTU")
}
out := make([]byte, 1+16+2+len(token)+4+16+2)
@@ -84,7 +93,7 @@ func ParseVPNOpen(payload []byte) (sid VPNSessionID, token string, ipv4, ipv6 ne
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 {
if len(payload) != need {
err = errors.New("bad VPN OPEN length")
return
}
@@ -103,6 +112,8 @@ func ParseVPNOpen(payload []byte) (sid VPNSessionID, token string, ipv4, ipv6 ne
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
@@ -110,9 +121,9 @@ func BuildVPNOpened(maxChunk int) []byte {
if maxChunk < 1 {
maxChunk = 1
}
out := make([]byte, 3)
out := make([]byte, 5)
out[0] = VPNRespOpened
binary.BigEndian.PutUint16(out[1:3], uint16(maxChunk))
binary.BigEndian.PutUint32(out[1:5], uint32(maxChunk))
return out
}
@@ -120,48 +131,55 @@ 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")
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
}
return int(binary.BigEndian.Uint16(payload[1:3])), 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 request: cmd(1) sid(16) seq(4) offset(2) total(2) data(N)
// 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 > 65535 || offset < 0 || offset > total || len(data) < 1 || offset+len(data) > total || len(data) > VPNMaxFragment {
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, 25+len(data))
out := make([]byte, 29+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)
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) < 26 || payload[0] != VPNCmdPush {
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.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 {
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, 7)
out := make([]byte, 9)
out[0] = VPNRespAck
binary.BigEndian.PutUint32(out[1:5], seq)
binary.BigEndian.PutUint16(out[5:7], uint16(accepted))
binary.BigEndian.PutUint32(out[5:9], uint32(accepted))
return out
}
@@ -170,54 +188,54 @@ func ParseVPNAck(payload []byte) (seq uint32, accepted int, err error) {
err = e
return
}
if len(payload) != 7 || payload[0] != VPNRespAck {
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.Uint16(payload[5:7]))
accepted = int(binary.BigEndian.Uint32(payload[5:9]))
return
}
// PULL request: cmd(1) sid(16) ack(4) want(4) offset(2) limit(2)
// 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 > 65535 || limit < 1 || limit > VPNMaxFragment {
if offset < 0 || offset > VPNMaxBatch || limit < 1 || limit > VPNMaxFragment {
return nil, errors.New("invalid VPN PULL")
}
out := make([]byte, 29)
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.PutUint16(out[25:27], uint16(offset))
binary.BigEndian.PutUint16(out[27:29], uint16(limit))
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) != 29 || payload[0] != VPNCmdPull {
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.Uint16(payload[25:27]))
limit = int(binary.BigEndian.Uint16(payload[27:29]))
if limit < 1 {
err = errors.New("bad VPN PULL limit")
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 response: cmd(1) seq(4) offset(2) total(2) data(N)
// 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, 9+len(data))
out := make([]byte, 13+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)
binary.BigEndian.PutUint32(out[5:9], uint32(offset))
binary.BigEndian.PutUint32(out[9:13], uint32(total))
copy(out[13:], data)
return out
}
@@ -230,20 +248,111 @@ func ParseVPNData(payload []byte) (seq uint32, offset, total int, data []byte, w
wait = true
return
}
if len(payload) < 10 || payload[0] != VPNRespData {
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.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 {
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