- stun: RFC 5389 binding responder + RFC 5780 attributes (OTHER-ADDRESS,
RESPONSE-ORIGIN, CHANGE-REQUEST) on a primary/alt-port socket grid per
address; advertises stun-5780 with >=2 same-family addrs, else
stun-basic. Unmodified framing for tooling interop. Tested.
- tcpecho: JSON greeting with observed src + TCP_INFO MSS/options
(Linux getsockopt; zeroed elsewhere via build tags), then byte echo.
- session: per-packet UDP observations + connect-back results, ByID lookup.
- control: GET /v1/sessions/{id}/observations, POST .../actions
(delayed_echo → DELAYED_ECHO at the observed data-plane source;
connect_back → dial the control-plane source, record connected/refused/
timeout+rtt). Capabilities computed from what is actually wired.
- config/main: comma-separated STUN listeners; all planes bind explicit
addresses; graceful shutdown of the new listeners.
Full flow smoke-tested; go test green (stun binding/change-port,
dataplane wire format).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
70 lines
1.5 KiB
Go
70 lines
1.5 KiB
Go
// 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,
|
|
}
|
|
}
|