diff --git a/pkg/interfaces/tcp.go b/pkg/interfaces/tcp.go index c7e8324..015c8e9 100644 --- a/pkg/interfaces/tcp.go +++ b/pkg/interfaces/tcp.go @@ -5,7 +5,6 @@ import ( "net" "runtime" "sync" - "syscall" "time" "github.com/Sudo-Ivan/reticulum-go/pkg/common" @@ -435,108 +434,6 @@ func (tc *TCPClientInterface) GetStats() (tx uint64, rx uint64, lastTx time.Time return tc.TxBytes, tc.RxBytes, tc.lastTx, tc.lastRx } -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: %v", err) - } - - var sockoptErr error - err = rawConn.Control(func(fd uintptr) { - var userTimeout, probeAfter, probeInterval, probeCount int - - if tc.i2pTunneled { - userTimeout = I2P_USER_TIMEOUT_SEC * 1000 - probeAfter = I2P_PROBE_AFTER_SEC - probeInterval = I2P_PROBE_INTERVAL_SEC - probeCount = I2P_PROBES_COUNT - } else { - userTimeout = TCP_USER_TIMEOUT_SEC * 1000 - probeAfter = TCP_PROBE_AFTER_SEC - probeInterval = TCP_PROBE_INTERVAL_SEC - probeCount = TCP_PROBES_COUNT - } - - if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, 18, userTimeout); err != nil { - debug.Log(debug.DEBUG_VERBOSE, "Failed to set TCP_USER_TIMEOUT", "error", err) - } - - if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1); err != nil { - sockoptErr = fmt.Errorf("failed to enable SO_KEEPALIVE: %v", err) - return - } - - if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, 4, probeAfter); err != nil { - debug.Log(debug.DEBUG_VERBOSE, "Failed to set TCP_KEEPIDLE", "error", err) - } - - if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, 5, probeInterval); err != nil { - debug.Log(debug.DEBUG_VERBOSE, "Failed to set TCP_KEEPINTVL", "error", err) - } - - if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, 6, probeCount); err != nil { - debug.Log(debug.DEBUG_VERBOSE, "Failed to set TCP_KEEPCNT", "error", err) - } - }) - - if err != nil { - return fmt.Errorf("control failed: %v", err) - } - if sockoptErr != nil { - return sockoptErr - } - - debug.Log(debug.DEBUG_VERBOSE, "TCP keepalive configured (Linux)", "i2p", tc.i2pTunneled) - return nil -} - -func (tc *TCPClientInterface) setTimeoutsOSX() 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: %v", err) - } - - var sockoptErr error - err = rawConn.Control(func(fd uintptr) { - const TCP_KEEPALIVE = 0x10 - - var probeAfter int - if tc.i2pTunneled { - probeAfter = I2P_PROBE_AFTER_SEC - } else { - probeAfter = TCP_PROBE_AFTER_SEC - } - - if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1); err != nil { - sockoptErr = fmt.Errorf("failed to enable SO_KEEPALIVE: %v", err) - return - } - - if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, TCP_KEEPALIVE, probeAfter); err != nil { - debug.Log(debug.DEBUG_VERBOSE, "Failed to set TCP_KEEPALIVE", "error", err) - } - }) - - if err != nil { - return fmt.Errorf("control failed: %v", err) - } - if sockoptErr != nil { - return sockoptErr - } - - debug.Log(debug.DEBUG_VERBOSE, "TCP keepalive configured (OSX)", "i2p", tc.i2pTunneled) - return nil -} type TCPServerInterface struct { BaseInterface diff --git a/pkg/interfaces/tcp_darwin.go b/pkg/interfaces/tcp_darwin.go new file mode 100644 index 0000000..88273e3 --- /dev/null +++ b/pkg/interfaces/tcp_darwin.go @@ -0,0 +1,64 @@ +// +build darwin + +package interfaces + +import ( + "fmt" + "net" + "syscall" + "time" + + "github.com/Sudo-Ivan/reticulum-go/pkg/debug" +) + +func (tc *TCPClientInterface) setTimeoutsLinux() error { + return tc.setTimeoutsOSX() +} + +func (tc *TCPClientInterface) setTimeoutsOSX() 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: %v", err) + } + + var sockoptErr error + err = rawConn.Control(func(fd uintptr) { + const TCP_KEEPALIVE = 0x10 + + var probeAfter int + if tc.i2pTunneled { + probeAfter = I2P_PROBE_AFTER_SEC + } else { + probeAfter = TCP_PROBE_AFTER_SEC + } + + if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1); err != nil { + sockoptErr = fmt.Errorf("failed to enable SO_KEEPALIVE: %v", err) + return + } + + if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, TCP_KEEPALIVE, probeAfter); err != nil { + debug.Log(debug.DEBUG_VERBOSE, "Failed to set TCP_KEEPALIVE", "error", err) + } + }) + + if err != nil { + return fmt.Errorf("control failed: %v", err) + } + if sockoptErr != nil { + return sockoptErr + } + + debug.Log(debug.DEBUG_VERBOSE, "TCP keepalive configured (OSX)", "i2p", tc.i2pTunneled) + return nil +} + +func platformGetRTT(fd uintptr) time.Duration { + return 0 +} + diff --git a/pkg/interfaces/tcp_freebsd.go b/pkg/interfaces/tcp_freebsd.go new file mode 100644 index 0000000..eb59626 --- /dev/null +++ b/pkg/interfaces/tcp_freebsd.go @@ -0,0 +1,38 @@ +// +build freebsd + +package interfaces + +import ( + "fmt" + "net" + "time" + + "github.com/Sudo-Ivan/reticulum-go/pkg/debug" +) + +func (tc *TCPClientInterface) setTimeoutsLinux() error { + tcpConn, ok := tc.conn.(*net.TCPConn) + if !ok { + return fmt.Errorf("not a TCP connection") + } + + if err := tcpConn.SetKeepAlive(true); err != nil { + return fmt.Errorf("failed to enable keepalive: %v", err) + } + + if err := tcpConn.SetKeepAlivePeriod(TCP_PROBE_INTERVAL * time.Second); err != nil { + debug.Log(debug.DEBUG_VERBOSE, "Failed to set keepalive period", "error", err) + } + + debug.Log(debug.DEBUG_VERBOSE, "TCP keepalive configured (FreeBSD)", "i2p", tc.i2pTunneled) + return nil +} + +func (tc *TCPClientInterface) setTimeoutsOSX() error { + return tc.setTimeoutsLinux() +} + +func platformGetRTT(fd uintptr) time.Duration { + return 0 +} + diff --git a/pkg/interfaces/tcp_linux.go b/pkg/interfaces/tcp_linux.go index 6e75590..7378961 100644 --- a/pkg/interfaces/tcp_linux.go +++ b/pkg/interfaces/tcp_linux.go @@ -1,32 +1,99 @@ -//go:build linux // +build linux package interfaces import ( + "fmt" + "net" "syscall" "time" "unsafe" + + "github.com/Sudo-Ivan/reticulum-go/pkg/debug" ) +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: %v", err) + } + + var sockoptErr error + err = rawConn.Control(func(fd uintptr) { + var userTimeout, probeAfter, probeInterval, probeCount int + + if tc.i2pTunneled { + userTimeout = I2P_USER_TIMEOUT_SEC * 1000 + probeAfter = I2P_PROBE_AFTER_SEC + probeInterval = I2P_PROBE_INTERVAL_SEC + probeCount = I2P_PROBES_COUNT + } else { + userTimeout = TCP_USER_TIMEOUT_SEC * 1000 + probeAfter = TCP_PROBE_AFTER_SEC + probeInterval = TCP_PROBE_INTERVAL_SEC + probeCount = TCP_PROBES_COUNT + } + + if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, 18, userTimeout); err != nil { + debug.Log(debug.DEBUG_VERBOSE, "Failed to set TCP_USER_TIMEOUT", "error", err) + } + + if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1); err != nil { + sockoptErr = fmt.Errorf("failed to enable SO_KEEPALIVE: %v", err) + return + } + + if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, 4, probeAfter); err != nil { + debug.Log(debug.DEBUG_VERBOSE, "Failed to set TCP_KEEPIDLE", "error", err) + } + + if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, 5, probeInterval); err != nil { + debug.Log(debug.DEBUG_VERBOSE, "Failed to set TCP_KEEPINTVL", "error", err) + } + + if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, 6, probeCount); err != nil { + debug.Log(debug.DEBUG_VERBOSE, "Failed to set TCP_KEEPCNT", "error", err) + } + }) + + if err != nil { + return fmt.Errorf("control failed: %v", err) + } + if sockoptErr != nil { + return sockoptErr + } + + debug.Log(debug.DEBUG_VERBOSE, "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 syscall.TCPInfo - size := uint32(syscall.SizeofTCPInfo) - - _, _, err := syscall.Syscall6( + infoLen := uint32(unsafe.Sizeof(info)) + + // TCP_INFO is 11 on Linux + _, _, errno := syscall.Syscall6( syscall.SYS_GETSOCKOPT, fd, - syscall.SOL_TCP, - syscall.TCP_INFO, - uintptr(unsafe.Pointer(&info)), // #nosec G103 - uintptr(unsafe.Pointer(&size)), // #nosec G103 + syscall.IPPROTO_TCP, + 11, // TCP_INFO + uintptr(unsafe.Pointer(&info)), + uintptr(unsafe.Pointer(&infoLen)), 0, ) - - if err != 0 { + + if errno != 0 { return 0 } - - // RTT is in microseconds, convert to Duration + return time.Duration(info.Rtt) * time.Microsecond } diff --git a/pkg/interfaces/tcp_windows.go b/pkg/interfaces/tcp_windows.go new file mode 100644 index 0000000..09d1b94 --- /dev/null +++ b/pkg/interfaces/tcp_windows.go @@ -0,0 +1,40 @@ +package interfaces + +import ( + "fmt" + "net" + "time" + + "github.com/Sudo-Ivan/reticulum-go/pkg/debug" +) + +func (tc *TCPClientInterface) setTimeoutsLinux() error { + return tc.setTimeoutsWindows() +} + +func (tc *TCPClientInterface) setTimeoutsOSX() error { + return tc.setTimeoutsWindows() +} + +func (tc *TCPClientInterface) setTimeoutsWindows() error { + tcpConn, ok := tc.conn.(*net.TCPConn) + if !ok { + return fmt.Errorf("not a TCP connection") + } + + if err := tcpConn.SetKeepAlive(true); err != nil { + return fmt.Errorf("failed to enable keepalive: %v", err) + } + + if err := tcpConn.SetKeepAlivePeriod(TCP_PROBE_INTERVAL); err != nil { + debug.Log(debug.DEBUG_VERBOSE, "Failed to set keepalive period", "error", err) + } + + debug.Log(debug.DEBUG_VERBOSE, "TCP keepalive configured (Windows)", "i2p", tc.i2pTunneled) + return nil +} + +func platformGetRTT(fd uintptr) time.Duration { + return 0 +} + diff --git a/pkg/interfaces/udp.go b/pkg/interfaces/udp.go index 6aa8b3d..6bf1c44 100644 --- a/pkg/interfaces/udp.go +++ b/pkg/interfaces/udp.go @@ -218,6 +218,7 @@ func (ui *UDPInterface) readLoop() { // Update RX stats ui.mutex.Lock() + // #nosec G115 - Network read sizes are always positive and within safe range ui.RxBytes += uint64(n) // Auto-discover target address from first packet if not set