Reticulum-Go/pkg/interfaces/tcp_linux.go

123 lines
3 KiB
Go

// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2024-2026 Quad4.io
//go:build linux
package interfaces
import (
"fmt"
"net"
"time"
"unsafe"
"golang.org/x/sys/unix"
"quad4/reticulum-go/pkg/debug"
)
func linuxFdToInt(fd uintptr) (int, bool) {
maxInt := int(^uint(0) >> 1)
if fd > uintptr(maxInt) {
return 0, false
}
// #nosec G115 -- fd is bounded above by max int, invalid fds rejected above
return int(fd), true
}
func (tc *TCPClientInterface) setTimeoutsLinux() error {
tcpConn, ok := tc.conn.(*net.TCPConn)
if !ok {
return fmt.Errorf("not a TCP connection")
}
rawConn, err := tcpConn.SyscallConn()
if err != nil {
return fmt.Errorf("failed to get raw connection: %w", err)
}
var sockoptErr error
err = rawConn.Control(func(fd uintptr) {
fdInt, ok := linuxFdToInt(fd)
if !ok {
sockoptErr = fmt.Errorf("invalid file descriptor")
return
}
var userTimeout, probeAfter, probeInterval, probeCount int
if tc.i2pTunneled {
userTimeout = I2PUserTimeoutSec * TCPMilliseconds
probeAfter = I2PProbeAfterSec
probeInterval = I2PProbeIntervalSec
probeCount = I2PProbesCount
} else {
userTimeout = TCPUserTimeoutSec * TCPMilliseconds
probeAfter = TCPProbeAfterSec
probeInterval = TCPProbeIntervalSec
probeCount = TCPProbesCount
}
const TCP_USER_TIMEOUT = 18
const TCP_KEEPIDLE = 4
const TCP_KEEPINTVL = 5
const TCP_KEEPCNT = 6
if err := unix.SetsockoptInt(fdInt, unix.IPPROTO_TCP, TCP_USER_TIMEOUT, userTimeout); err != nil {
debug.Log(debug.DebugVerbose, "Failed to set TCP_USER_TIMEOUT", "error", err)
}
if err := unix.SetsockoptInt(fdInt, unix.SOL_SOCKET, unix.SO_KEEPALIVE, SOKeepaliveEnable); err != nil {
sockoptErr = fmt.Errorf("failed to enable SO_KEEPALIVE: %w", err)
return
}
if err := unix.SetsockoptInt(fdInt, unix.IPPROTO_TCP, TCP_KEEPIDLE, probeAfter); err != nil {
debug.Log(debug.DebugVerbose, "Failed to set TCP_KEEPIDLE", "error", err)
}
if err := unix.SetsockoptInt(fdInt, unix.IPPROTO_TCP, TCP_KEEPINTVL, probeInterval); err != nil {
debug.Log(debug.DebugVerbose, "Failed to set TCP_KEEPINTVL", "error", err)
}
if err := unix.SetsockoptInt(fdInt, unix.IPPROTO_TCP, TCP_KEEPCNT, probeCount); err != nil {
debug.Log(debug.DebugVerbose, "Failed to set TCP_KEEPCNT", "error", err)
}
})
if err != nil {
return fmt.Errorf("control failed: %w", err)
}
if sockoptErr != nil {
return sockoptErr
}
debug.Log(debug.DebugVerbose, "TCP keepalive configured (Linux)", "i2p", tc.i2pTunneled)
return nil
}
func (tc *TCPClientInterface) setTimeoutsOSX() error {
return tc.setTimeoutsLinux()
}
func platformGetRTT(fd uintptr) time.Duration {
var info unix.TCPInfo
infoLen := uint32(unsafe.Sizeof(info))
const TCP_INFO = 11
// #nosec G103
_, _, errno := unix.Syscall6(
unix.SYS_GETSOCKOPT,
fd,
unix.IPPROTO_TCP,
TCP_INFO,
uintptr(unsafe.Pointer(&info)),
uintptr(unsafe.Pointer(&infoLen)),
0,
)
if errno != 0 {
return 0
}
return time.Duration(info.Rtt) * time.Microsecond
}