mirror of
https://github.com/qdm12/gluetun.git
synced 2026-05-07 04:20:12 +02:00
158 lines
5.1 KiB
Go
158 lines
5.1 KiB
Go
package tcp
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"math/rand/v2"
|
|
"net/netip"
|
|
"syscall"
|
|
|
|
"github.com/qdm12/gluetun/internal/pmtud/constants"
|
|
"github.com/qdm12/gluetun/internal/pmtud/ip"
|
|
)
|
|
|
|
// createSYNPacket creates a TCP SYN packet for initiating a handshake.
|
|
// SYN packets have normally no data payload, so you SHOULD set mtu to 0.
|
|
// However, in some cases where the server closes the connection with RST immediately,
|
|
// it can be useful to add some data payload to a SYN packet and check if the server still
|
|
// replies. Only set mtu to a non zero value if you know what you are doing.
|
|
func createSYNPacket(src, dst netip.AddrPort, mtu uint32) (packet []byte, seq uint32) {
|
|
seq = rand.Uint32() //nolint:gosec
|
|
const ack = 0 // SYN has no ACK number
|
|
payloadLength := constants.BaseTCPHeaderLength // no data payload
|
|
if mtu > 0 {
|
|
payloadLength = getPayloadLength(mtu, dst)
|
|
}
|
|
return createPacket(src, dst, seq, ack, payloadLength, synFlag), seq
|
|
}
|
|
|
|
// createACKPacket creates a TCP ACK packet.
|
|
// If the mtu is set to 0, no payload is sent.
|
|
// Otherwise, the payload is calculated to test the MTU given.
|
|
func createACKPacket(src, dst netip.AddrPort, seq, ack uint32, mtu uint32) []byte {
|
|
payloadLength := constants.BaseTCPHeaderLength // no data payload
|
|
if mtu > 0 {
|
|
payloadLength = getPayloadLength(mtu, dst)
|
|
}
|
|
const flags = ackFlag | pshFlag
|
|
return createPacket(src, dst, seq, ack, payloadLength, flags)
|
|
}
|
|
|
|
func createRSTPacket(src, dst netip.AddrPort, seq, ack uint32) []byte {
|
|
const payloadLength = constants.BaseTCPHeaderLength // no data payload
|
|
return createPacket(src, dst, seq, ack, payloadLength, rstFlag)
|
|
}
|
|
|
|
func getPayloadLength(mtu uint32, dst netip.AddrPort) uint32 {
|
|
var ipHeaderLength uint32
|
|
if dst.Addr().Is4() {
|
|
ipHeaderLength = constants.IPv4HeaderLength
|
|
} else {
|
|
ipHeaderLength = constants.IPv6HeaderLength
|
|
}
|
|
if mtu < ipHeaderLength+constants.BaseTCPHeaderLength {
|
|
panic("MTU too small to hold IP and TCP headers")
|
|
}
|
|
return mtu - ipHeaderLength
|
|
}
|
|
|
|
func createPacket(src, dst netip.AddrPort,
|
|
seq, ack, payloadLength uint32, flags byte,
|
|
) []byte {
|
|
if payloadLength < constants.BaseTCPHeaderLength {
|
|
panic("payload length is too small to hold TCP header")
|
|
}
|
|
|
|
var ipHeader []byte
|
|
if dst.Addr().Is4() {
|
|
ipHeader = ip.HeaderV4(src.Addr(), dst.Addr(), payloadLength)
|
|
} else {
|
|
ipHeader = ip.HeaderV6(src.Addr(), dst.Addr(),
|
|
uint16(payloadLength), byte(syscall.IPPROTO_TCP)) //nolint:gosec
|
|
}
|
|
|
|
tcpHeader := makeTCPHeader(src.Port(), dst.Port(), seq, ack, flags)
|
|
|
|
// data is just zeroes
|
|
dataLength := int(payloadLength - constants.BaseTCPHeaderLength)
|
|
var data []byte
|
|
if dataLength > 0 {
|
|
data = generatePayload(uint16(dataLength)) //nolint:gosec
|
|
}
|
|
checksum := tcpChecksum(ipHeader, tcpHeader, data)
|
|
tcpHeader[16] = byte(checksum >> 8) //nolint:mnd
|
|
tcpHeader[17] = byte(checksum & 0xff) //nolint:mnd
|
|
|
|
packet := make([]byte, len(ipHeader)+int(constants.BaseTCPHeaderLength)+dataLength)
|
|
copy(packet, ipHeader)
|
|
copy(packet[len(ipHeader):], tcpHeader)
|
|
copy(packet[len(ipHeader)+int(constants.BaseTCPHeaderLength):], data)
|
|
return packet
|
|
}
|
|
|
|
// generatePayload creates a byte slice of 'length' size.
|
|
// For lengths below 88B, it returns pseudo random data.
|
|
// For lengths above, it returns a structured TLS Client Hello with padding,
|
|
// which is more likely to be accepted by servers and not trigger RST replies.
|
|
//
|
|
//nolint:mnd
|
|
func generatePayload(length uint16) []byte {
|
|
const minTLSClientHelloSize = 5 + // TLS record
|
|
4 + // handshake header
|
|
67 + // client hello
|
|
4 + // cipher suites
|
|
2 + // compression methods
|
|
2 + // extensions length
|
|
4 // padding extension header
|
|
if length < minTLSClientHelloSize {
|
|
data := make([]byte, length)
|
|
makeRandom(data)
|
|
return data
|
|
}
|
|
|
|
payload := make([]byte, length)
|
|
|
|
// --- TLS Record Layer ---
|
|
payload[0] = 0x16 // Handshake
|
|
payload[1] = 0x03 // Version 3.1
|
|
payload[2] = 0x01
|
|
binary.BigEndian.PutUint16(payload[3:5], length-5)
|
|
|
|
// --- Handshake Header ---
|
|
payload[5] = 0x01 // Client Hello
|
|
handshakeLength := make([]byte, 4)
|
|
// TLS Handshake length is 24-bit.
|
|
// We use a 4-byte buffer and copy the trailing 3 bytes.
|
|
binary.BigEndian.PutUint32(handshakeLength, uint32(length-9))
|
|
copy(payload[6:9], handshakeLength[1:])
|
|
|
|
// --- Client Hello Body ---
|
|
payload[9] = 0x03 // Version 3.3 (TLS 1.2)
|
|
payload[10] = 0x03
|
|
makeRandom(payload[11:43]) // 32 bytes of random
|
|
payload[43] = 32 // Session ID length
|
|
|
|
// Cipher Suites (Length: 2, Data: 2)
|
|
binary.BigEndian.PutUint16(payload[44:46], 2)
|
|
binary.BigEndian.PutUint16(payload[46:48], 0x009c) // TLS_RSA_WITH_AES_128_GCM_SHA256
|
|
|
|
payload[48] = 0x01 // Compression length
|
|
payload[49] = 0x00 // Null compression
|
|
|
|
// --- Extensions ---
|
|
binary.BigEndian.PutUint16(payload[50:52], length-52) // extension length
|
|
|
|
// --- Padding Extension (Type 21) ---
|
|
binary.BigEndian.PutUint16(payload[52:54], 21)
|
|
const bytesUsedSoFar = 88
|
|
paddingDataLength := length - bytesUsedSoFar
|
|
binary.BigEndian.PutUint16(payload[54:56], paddingDataLength)
|
|
|
|
return payload
|
|
}
|
|
|
|
func makeRandom(b []byte) {
|
|
for i := range b {
|
|
b[i] = byte(rand.Uint32()) //nolint:gosec
|
|
}
|
|
}
|