Documentation
¶
Overview ¶
fectun —— socat 式 TCP 隧道,跨境段走 FEC over UDP 设计要点:
- FEC(Reed-Solomon)把链路 10~20% 丢包压到近零,大幅减少重传触发
- NACK-based ARQ 兜底 FEC 未能恢复的残余丢包(TCP 字节流必须完整有序)
- 内建心跳维持 NAT/安全组 conntrack —— 实测单向 UDP 会被阿里云安全组全丢
- 令牌桶速率控制 —— 实测持续满负载会把链路丢包从 10% 推到 30%,FEC 会崩
Index ¶
Constants ¶
const ( DefaultK = 20 DefaultM = 15 DefaultRateMbps = 25 )
与 CLI 默认值保持一致。
const ( DefaultIdleTimeout = 30 * time.Second DefaultMaxPeers = 64 )
Variables ¶
var Logf = func(format string, args ...any) { fmt.Printf(format+"\n", args...) }
Logf 是库内所有日志的出口,默认打到 stdout。 嵌入方(例如 stdout 另有用途的进程)可以换成自己的 logger,或置为 nil 静默。
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client 是入口侧:每个 OpenStream 在隧道里开一条 stream, 对端(Server)为它连一次 target。
一个 Client 独占一个 UDP socket 和一套限速。同一对端上的多条连接应共用 一个 Client,而不是各建一个 —— 否则每个 Client 各按 RateMbps 发, 总速率成倍超出限速。
func NewClient ¶
NewClient 在 conn 上建立到 peer 的隧道。conn 由调用方创建,这样调用方能在 交出之前对 socket 做平台相关处理(例如 Android 的 VpnService.protect)。 Client 接管 conn 的所有权,Close 时一并关闭。
func (*Client) HandleConn ¶
HandleConn 把一条现成的连接(例如 CLI 接受的 TCP 连接)接进隧道。 与 OpenStream 的区别是:下游是真 TCP 时半关闭会透传(CloseWrite)。
func (*Client) OpenStream ¶
OpenStream 开一条新 stream,返回它的本地一端。
读写语义与一条 TCP 连接相同;Close 即结束该 stream。 RemoteAddr 返回隧道对端的 UDP 地址。
type Options ¶
Options 是一端的 FEC 与限速参数。
K/M 两端必须一致:接收侧按本端的 K 解组,对不上的包直接丢弃。 RateMbps 是本端发送方向的线路限速(含冗余),净数据 ≈ RateMbps/(1+M/K)。 不要超过链路当时的实际容量 —— 超发只会加剧丢包,实测曾把同机 SSH 挤断。
RateMinMbps > 0 时打开拥塞控制:RateMbps 变成上限,实际速率按对端回报的丢包率 在 [RateMinMbps, RateMbps] 内自动调(见 congCtl)。为 0 时按 RateMbps 固定发, 与旧版行为相同。对端是不回反馈的旧版本时也自动退回固定速率。
Key 是可选的预共享密钥:非空时每个包带 HMAC 签名,验签失败的包直接丢弃 (见 packetAuth)。两端必须一致;为空时线格式与旧版相同。
type PeerServer ¶
type PeerServer struct {
// contains filtered or unexported fields
}
PeerServer 是落地侧的固定对端模式:只与 -peer 指定的一个对端通信。 两端都是云主机、安全组默认拒绝入站 UDP 时,靠双方同时发心跳打洞 —— 这要求 落地侧从一开始就知道对端地址,所以保留这一模式。
func NewPeerServer ¶
func NewPeerServer(conn *net.UDPConn, peer *net.UDPAddr, target string, o Options) (*PeerServer, error)
NewPeerServer 在 conn 上服务固定对端 peer,每条 stream 连一次 target。
func (*PeerServer) Close ¶
func (p *PeerServer) Close() error
func (*PeerServer) Stats ¶
func (p *PeerServer) Stats() string
type Server ¶
type Server struct {
// IdleTimeout:对端多久没有任何包(心跳 100ms 一个)就回收其会话。
IdleTimeout time.Duration
// MaxPeers:同时存在的会话上限。超出时新对端的包直接丢弃,
// 防止伪造源地址的包把内存和 goroutine 撑爆。
MaxPeers int
// contains filtered or unexported fields
}
Server 是落地侧的多对端模式:不预设对端地址,按 UDP 源地址为每个对端 各建一个会话。
这是给 NAT 后面、IP 会变的客户端(笔记本、手机)用的:客户端先发包, 服务端从源地址学到该往哪回。代价是落地侧的安全组必须放行入站 UDP —— 它没法像 PeerServer 那样靠双向心跳打洞,因为事先不知道对端是谁。
k/m 由客户端决定:会话用对端第一个包头里的 k/m 建立(心跳与数据包都带), 所以 Options 里的 K/M 在这里不起作用,只有 RateMbps 是每个会话的发送限速。 注意限速是逐会话的,N 个对端同时满载时总发送量是 N×RateMbps。
客户端换了源地址(换网、NAT 映射过期)在服务端看来就是一个新对端:新会话 的 epoch 不同,客户端据此重置并断开旧 stream,上层重连即可;旧会话空闲超时 后回收。