Files
anubis-mirror/internal/fingerprint/tcp_linux.go
2025-07-18 20:38:19 +00:00

133 lines
2.7 KiB
Go

//go:build linux
package fingerprint
import (
"fmt"
"net"
"syscall"
"unsafe"
)
type tcpInfo struct {
State uint8
Ca_state uint8
Retransmits uint8
Probes uint8
Backoff uint8
Options uint8
Wnd_scale uint8
Delivery_rate_app_limited uint8
Rto uint32
Ato uint32
SndMss uint32
RcvMss uint32
Unacked uint32
Sacked uint32
Lost uint32
Retrans uint32
Fackets uint32
Last_data_sent uint32
Last_ack_sent uint32
Last_data_recv uint32
Last_ack_recv uint32
PMTU uint32
Rcv_ssthresh uint32
RTT uint32
RTTvar uint32
Snd_ssthresh uint32
Snd_cwnd uint32
Advmss uint32
Reordering uint32
Rcv_rtt uint32
Rcv_space uint32
Total_retrans uint32
Pacing_rate uint64
Max_pacing_rate uint64
Bytes_acked uint64
Bytes_received uint64
Segs_out uint32
Segs_in uint32
Notsent_bytes uint32
Min_rtt uint32
Data_segs_in uint32
Data_segs_out uint32
Delivery_rate uint64
Busy_time uint64
Rwnd_limited uint64
Sndbuf_limited uint64
Delivered uint32
Delivered_ce uint32
Bytes_sent uint64
Bytes_retrans uint64
DSACK_dups uint32
Reord_seen uint32
Rcv_ooopack uint32
Snd_wnd uint32
}
// AssignTCPFingerprint extracts TCP fingerprint information from a connection
func AssignTCPFingerprint(conn net.Conn) (*JA4T, error) {
tcpConn, ok := conn.(*net.TCPConn)
if !ok {
return nil, fmt.Errorf("not a TCPConn")
}
rawConn, err := tcpConn.SyscallConn()
if err != nil {
return nil, fmt.Errorf("SyscallConn failed: %w", err)
}
var info tcpInfo
var sysErr error
err = rawConn.Control(func(fd uintptr) {
size := uint32(unsafe.Sizeof(info))
_, _, errno := syscall.Syscall6(
syscall.SYS_GETSOCKOPT,
fd,
uintptr(syscall.IPPROTO_TCP),
uintptr(syscall.TCP_INFO),
uintptr(unsafe.Pointer(&info)),
uintptr(unsafe.Pointer(&size)),
0,
)
if errno != 0 {
sysErr = errno
}
})
if err != nil {
return nil, fmt.Errorf("SyscallConn.Control: %w", err)
}
if sysErr != nil {
return nil, fmt.Errorf("getsockopt TCP_INFO: %w", sysErr)
}
fp := &JA4T{
Window: info.Snd_wnd,
MSS: uint16(info.SndMss),
}
const (
TCPI_OPT_TIMESTAMPS = 1 << 0
TCPI_OPT_SACK = 1 << 1
TCPI_OPT_WSCALE = 1 << 2
)
if info.Options&TCPI_OPT_SACK != 0 {
fp.Options = append(fp.Options, 4, 1)
}
if info.Options&TCPI_OPT_TIMESTAMPS != 0 {
fp.Options = append(fp.Options, 8, 1)
}
if info.Options&TCPI_OPT_WSCALE != 0 {
fp.Options = append(fp.Options, 3)
fp.WindowScale = info.Wnd_scale
}
return fp, nil
}