// SPDX-FileCopyrightText: 2026 Echolot contributors // SPDX-License-Identifier: GPL-3.0-or-later //go:build linux package tcpecho import ( "encoding/binary" "net" "syscall" "unsafe" ) type connInfo struct { MSS int Options []string } // tcpInfo reads TCP_INFO via getsockopt. Only the head of struct tcp_info is // needed: 8 header bytes (state..wscale flags) then u32 rto, ato, snd_mss, // rcv_mss — layout is part of the kernel ABI and stable. func tcpInfo(conn net.Conn) connInfo { tc, ok := conn.(*net.TCPConn) if !ok { return connInfo{} } raw, err := tc.SyscallConn() if err != nil { return connInfo{} } var buf [104]byte var got bool _ = raw.Control(func(fd uintptr) { l := uint32(len(buf)) _, _, errno := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, uintptr(syscall.SOL_TCP), uintptr(syscall.TCP_INFO), uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&l)), 0) got = errno == 0 && l >= 24 }) if !got { return connInfo{} } // tcpi_options bit flags (include/uapi/linux/tcp.h) const ( optTimestamps = 1 optSACK = 2 optWscale = 4 optECN = 8 ) var opts []string ob := buf[5] if ob&optTimestamps != 0 { opts = append(opts, "timestamps") } if ob&optSACK != 0 { opts = append(opts, "sack") } if ob&optWscale != 0 { opts = append(opts, "wscale") } if ob&optECN != 0 { opts = append(opts, "ecn") } return connInfo{ MSS: int(binary.LittleEndian.Uint32(buf[16:20])), // tcpi_snd_mss Options: opts, } }