init: 自 zomaintain/backend/zonat 平移独立成库; module path 改为 git.zeroonesoft.cn/golib/zonat; 内部 import 同步重写

This commit is contained in:
w11
2026-09-19 18:55:43 +08:00
commit 984041f1a0
20 changed files with 2427 additions and 0 deletions
+304
View File
@@ -0,0 +1,304 @@
// tunnel.go 一条隧道的节点连接 worker:每条隧道独立完成
// 连接→登录(携带自身 Token)→smux→控制流注册自身→数据流服务,
// 因此天然支持不同隧道连接不同节点。
// 永久隧道断线按 Backoff 重连重注册;临时隧道一次性退出。
package agent
import (
"errors"
"fmt"
"net"
"sync"
"time"
"github.com/xtaci/smux"
)
const (
loginTimeout = 10 * time.Second // 登录握手超时(含拨号)
registerWait = 30 * time.Second // RegisterTunnel 等待首次注册完成的上限
)
// errTunnelClosed 节点推送 CmdTunnelClosed:隧道已被节点删除(TTL 过期等)
var errTunnelClosed = errors.New("隧道已被节点关闭")
// tunnelConn 一条隧道的全部运行态:期望配置 + 到该隧道节点的桥接连接
type tunnelConn struct {
ag *Agent
t Tunnel
stopCh chan struct{}
stopOnce sync.Once
firstOnce sync.Once
first chan struct{} // 首次"连接+登录+注册"尝试结束后关闭
firstPort int
firstErr error
everRegistered bool // 本次注册成功过至少一次(run 协程独占读写)
mu sync.Mutex
conn net.Conn
session *smux.Session
control net.Conn
ctrlMu sync.Mutex // 控制流"写请求+读应答"串行化
}
func newTunnelConn(ag *Agent, t Tunnel) *tunnelConn {
return &tunnelConn{
ag: ag,
t: t,
stopCh: make(chan struct{}),
first: make(chan struct{}),
}
}
// markFirst 记录首次注册尝试的结果;channel close 保证读取方看到字段写入
func (w *tunnelConn) markFirst(port int, err error) {
w.firstOnce.Do(func() {
w.firstPort, w.firstErr = port, err
close(w.first)
})
}
// run 重连主循环,阻塞直到停止/一次性结束
func (w *tunnelConn) run() {
for {
port, err := w.connect()
w.markFirst(port, err)
if err == nil {
err = w.serve()
}
w.teardown()
if w.isStopped() || w.ag.isClosed() {
return
}
if errors.Is(err, errTunnelClosed) {
w.ag.removeTunnelState(w.t.Id)
return
}
if w.t.TTLSec > 0 {
// 临时隧道一次性:连接断开即结束(节点侧随会话移除)
if w.everRegistered {
w.ag.removeTunnelState(w.t.Id)
}
return
}
w.ag.logger().Warnf("agent 隧道连接断开 id=%s node=%s err=%s backoff=%s",
w.t.Id, w.t.NodeAddr, errText(err), w.ag.Backoff)
select {
case <-time.After(w.ag.Backoff):
case <-w.stopCh:
return
}
}
}
// connect 连接并登录该隧道的节点,经控制流注册自身,返回节点分配端口
func (w *tunnelConn) connect() (int, error) {
conn, err := net.DialTimeout("tcp", w.t.NodeAddr, loginTimeout)
if err != nil {
return 0, err
}
// 登录(同 zonat:CmdLogin 帧 + Ret 应答)
lf := NewFrame(FrameVersion, CmdLogin, 0)
if err := lf.Marshal(Login{AgentId: w.ag.AgentId, Token: w.t.Token, Version: Version}); err != nil {
conn.Close()
return 0, err
}
if err := WriteFrame(conn, lf); err != nil {
conn.Close()
return 0, err
}
conn.SetReadDeadline(time.Now().Add(loginTimeout))
rf, err := ReadFrame(conn)
if err != nil {
conn.Close()
return 0, err
}
ret := Ret{}
if err := rf.Unmarshal(&ret); err != nil {
conn.Close()
return 0, err
}
if ret.Code != 0 {
conn.Close()
return 0, fmt.Errorf("登录失败:%s", ret.Msg)
}
conn.SetReadDeadline(time.Time{})
sess, err := smux.Client(conn, SmuxConfig())
if err != nil {
conn.Close()
return 0, err
}
control, err := sess.OpenStream()
if err != nil {
sess.Close()
return 0, err
}
w.mu.Lock()
w.conn, w.session, w.control = conn, sess, control
w.mu.Unlock()
// 注册自身;凭据只用于登录,不随注册/数据帧回发
wire := w.t
wire.NodeAddr, wire.Token = "", ""
f, err := w.callControl(func(control net.Conn) error {
rf := NewFrame(FrameVersion, CmdRegisterTunnel, 0)
if err := rf.Marshal(wire); err != nil {
return err
}
return WriteFrame(control, rf)
}, CmdRegisterTunnel)
if err != nil {
return 0, err
}
rret := Ret{}
if err := f.Unmarshal(&rret); err != nil {
return 0, err
}
if rret.Code != 0 {
return 0, errors.New(rret.Msg)
}
w.everRegistered = true
w.ag.notifyPort(w.t.Id, rret.Port)
w.ag.logger().Infof("agent 隧道已连接节点 id=%s node=%s listen=%d",
w.t.Id, w.t.NodeAddr, rret.Port)
if w.ag.PingEvery > 0 {
go w.pingLoop()
}
return rret.Port, nil
}
// serve 数据流循环(阻塞;出错即断线,由 run 重连)
func (w *tunnelConn) serve() error {
w.mu.Lock()
sess := w.session
w.mu.Unlock()
if sess == nil {
return errors.New("未连接节点")
}
for {
stream, err := sess.AcceptStream()
if err != nil {
return err
}
go w.ag.handleData(stream)
}
}
func (w *tunnelConn) pingLoop() {
ticker := time.NewTicker(w.ag.PingEvery)
defer ticker.Stop()
for {
select {
case <-w.stopCh:
return
case <-ticker.C:
}
_, err := w.callControl(func(control net.Conn) error {
return WriteFrame(control, NewFrame(FrameVersion, CmdPing, 0))
}, CmdPing)
if err != nil {
if errors.Is(err, errTunnelClosed) {
// 节点已删除本隧道:杀掉会话让 serve 退出,run 收尾
w.teardown()
}
return
}
}
}
// callControl 串行化控制流交互。等待应答期间收到本隧道的关闭推送即返回 errTunnelClosed。
func (w *tunnelConn) callControl(write func(control net.Conn) error, wantCmd byte) (Frame, error) {
w.ctrlMu.Lock()
defer w.ctrlMu.Unlock()
w.mu.Lock()
control := w.control
w.mu.Unlock()
if control == nil {
return Frame{}, errors.New("未连接节点")
}
if err := write(control); err != nil {
return Frame{}, err
}
for {
f, err := ReadFrame(control)
if err != nil {
return Frame{}, err
}
if f.Cmd() == CmdTunnelClosed {
t := Tunnel{}
_ = f.Unmarshal(&t)
if t.Id == w.t.Id {
return Frame{}, errTunnelClosed
}
continue
}
if f.Cmd() == wantCmd {
return f, nil
}
// 其它命令忽略
}
}
// unregister 经控制流注销本隧道。传输失败视为成功:
// 连接已断时节点会随会话移除隧道,无需再通知。
func (w *tunnelConn) unregister() error {
w.mu.Lock()
connected := w.control != nil
w.mu.Unlock()
if !connected {
return nil
}
f, err := w.callControl(func(control net.Conn) error {
rf := NewFrame(FrameVersion, CmdUnregisterTunnel, 0)
if err := rf.Marshal(Tunnel{Id: w.t.Id}); err != nil {
return err
}
return WriteFrame(control, rf)
}, CmdUnregisterTunnel)
if err != nil {
return nil
}
ret := Ret{}
if err := f.Unmarshal(&ret); err != nil {
return nil
}
if ret.Code != 0 {
return errors.New(ret.Msg)
}
return nil
}
func (w *tunnelConn) stop() {
w.stopOnce.Do(func() { close(w.stopCh) })
w.teardown()
}
func (w *tunnelConn) isStopped() bool {
select {
case <-w.stopCh:
return true
default:
return false
}
}
func (w *tunnelConn) teardown() {
w.mu.Lock()
sess, conn := w.session, w.conn
w.session, w.conn, w.control = nil, nil, nil
w.mu.Unlock()
if sess != nil {
sess.Close()
}
if conn != nil {
conn.Close()
}
}