init: 自 zomaintain/backend/zonat 平移独立成库; module path 改为 git.zeroonesoft.cn/golib/zonat; 内部 import 同步重写
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
// jwt.go agent 登录令牌的 JWT 校验与签发(HS256)。
|
||||
// 正式部署由签发方(如 cloud)生成 token 下发给各隧道;节点只做校验。
|
||||
// SignToken 供 cmd/node -print-token(运维手工签发)与测试使用。
|
||||
package node
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
// verifyToken 校验 agent 登录 JWT:
|
||||
// - HS256/384/512 签名(显式白名单,拒绝 none 与其它算法)
|
||||
// - 必须带 exp 且未过期
|
||||
// - 若带 agentId 或 sub claim,则必须与登录 AgentId 一致(防跨 agent 冒用)
|
||||
func verifyToken(token, secret, agentId string) error {
|
||||
if secret == "" {
|
||||
return errors.New("节点未配置JWT密钥")
|
||||
}
|
||||
parsed, err := jwt.Parse(token, func(t *jwt.Token) (any, error) {
|
||||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("签名算法不允许:%v", t.Header["alg"])
|
||||
}
|
||||
return []byte(secret), nil
|
||||
}, jwt.WithValidMethods([]string{
|
||||
jwt.SigningMethodHS256.Alg(),
|
||||
jwt.SigningMethodHS384.Alg(),
|
||||
jwt.SigningMethodHS512.Alg(),
|
||||
}), jwt.WithExpirationRequired())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
claims, ok := parsed.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
return errors.New("claims类型错误")
|
||||
}
|
||||
for _, k := range []string{"agentId", "sub"} {
|
||||
if v, ok := claims[k].(string); ok && v != "" && v != agentId {
|
||||
return fmt.Errorf("token %s 与登录agentId不一致", k)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignToken 用节点密钥签发一张 agent 登录 JWT。
|
||||
// ttl<=0 会得到立即过期的 token(仅测试构造过期样例时有意义)。
|
||||
func SignToken(secret, agentId string, ttl time.Duration) (string, error) {
|
||||
if secret == "" {
|
||||
return "", errors.New("未配置JWT密钥")
|
||||
}
|
||||
now := time.Now()
|
||||
claims := jwt.MapClaims{
|
||||
"agentId": agentId,
|
||||
"iat": now.Unix(),
|
||||
"exp": now.Add(ttl).Unix(),
|
||||
}
|
||||
return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(secret))
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
// Package node 轻量穿透节点(zonat server 的裁剪版):
|
||||
// TCP 桥接登录(JWT)→ smux 会话 → 隧道注册表(纯内存)→ 纯 TCP 隧道监听。
|
||||
// 相比 zonat server 裁掉:KCP 桥接、udp/http(s) 隧道、协议嗅探与兜底站、
|
||||
// 管理 REST、数据库、限流与流量统计。
|
||||
// 隧道生命周期由 agent 经控制流帧命令注册/注销,节点不再对外暴露任何管理端口。
|
||||
// 一个 agent 的多条隧道各持一条桥接连接(可分布多节点),因此同一 agentId
|
||||
// 同时存在多个会话是常态;同名隧道顶替按"隧道 ID + agentId"粒度在注册时进行。
|
||||
package node
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.zeroonesoft.cn/golib/zonat/agent"
|
||||
)
|
||||
|
||||
// loginTimeout 登录握手读超时
|
||||
const loginTimeout = 10 * time.Second
|
||||
|
||||
// Node 穿透节点
|
||||
type Node struct {
|
||||
JwtSecret string // agent 登录 JWT 校验密钥(HS256 系)
|
||||
BindTunnel string // 隧道监听绑定地址(默认 0.0.0.0)
|
||||
SweepInterval time.Duration // TTL 清扫间隔(默认 30s)
|
||||
|
||||
listener net.Listener
|
||||
|
||||
mu sync.Mutex
|
||||
stopped bool
|
||||
sessions map[*Session]struct{} // 该节点上的全部 agent 会话(一个 agent 多隧道=多会话)
|
||||
tunnels map[string]*TunnelServer // tunnelId -> 隧道
|
||||
}
|
||||
|
||||
// New 创建节点
|
||||
func New() *Node {
|
||||
return &Node{
|
||||
BindTunnel: "0.0.0.0",
|
||||
SweepInterval: 30 * time.Second,
|
||||
sessions: make(map[*Session]struct{}),
|
||||
tunnels: make(map[string]*TunnelServer),
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动桥接监听与 TTL 清扫
|
||||
func (n *Node) Start(addr string) error {
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.listener = l
|
||||
slog.Info("节点 桥接监听", "addr", l.Addr().String())
|
||||
go n.acceptLoop()
|
||||
go n.sweepLoop()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop 停止节点,关闭所有隧道与会话
|
||||
func (n *Node) Stop() {
|
||||
n.mu.Lock()
|
||||
if n.stopped {
|
||||
n.mu.Unlock()
|
||||
return
|
||||
}
|
||||
n.stopped = true
|
||||
sessions := make([]*Session, 0, len(n.sessions))
|
||||
for s := range n.sessions {
|
||||
sessions = append(sessions, s)
|
||||
}
|
||||
tunnels := make([]*TunnelServer, 0, len(n.tunnels))
|
||||
for _, t := range n.tunnels {
|
||||
tunnels = append(tunnels, t)
|
||||
}
|
||||
n.mu.Unlock()
|
||||
|
||||
if n.listener != nil {
|
||||
n.listener.Close()
|
||||
}
|
||||
for _, t := range tunnels {
|
||||
t.close()
|
||||
}
|
||||
for _, s := range sessions {
|
||||
s.close()
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Node) isStopped() bool {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
return n.stopped
|
||||
}
|
||||
|
||||
// Addr 桥接监听地址
|
||||
func (n *Node) Addr() string {
|
||||
if n.listener == nil {
|
||||
return ""
|
||||
}
|
||||
return n.listener.Addr().String()
|
||||
}
|
||||
|
||||
func (n *Node) acceptLoop() {
|
||||
for {
|
||||
conn, err := n.listener.Accept()
|
||||
if err != nil {
|
||||
if !n.isStopped() {
|
||||
slog.Warn("节点 桥接接受连接错误", "err", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
go n.handleConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
// handleConn 登录握手(JWT 校验)+ 建立 smux 会话。
|
||||
// 新版 agent 每条隧道一条连接,同一 agentId 的多条会话并存。
|
||||
func (n *Node) handleConn(conn net.Conn) {
|
||||
conn.SetReadDeadline(time.Now().Add(loginTimeout))
|
||||
f, err := agent.ReadFrame(conn)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
if f.Cmd() != agent.CmdLogin {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
lg := agent.Login{}
|
||||
if err := f.Unmarshal(&lg); err != nil || lg.AgentId == "" {
|
||||
_ = writeRet(conn, agent.CmdLogin, 0, agent.Ret{Code: 1, Msg: "登录失败"})
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
if err := verifyToken(lg.Token, n.JwtSecret, lg.AgentId); err != nil {
|
||||
slog.Warn("节点 agent登录拒绝", "agent", lg.AgentId, "err", err.Error())
|
||||
_ = writeRet(conn, agent.CmdLogin, 0, agent.Ret{Code: 1, Msg: "登录失败"})
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
if err := writeRet(conn, agent.CmdLogin, 0, agent.Ret{Code: 0, Msg: "登录成功"}); err != nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
// smux 自管超时,清除登录用的读超时
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
|
||||
sess, err := smuxServer(conn)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
s := &Session{agentId: lg.AgentId, node: n, session: sess}
|
||||
n.mu.Lock()
|
||||
if n.stopped {
|
||||
n.mu.Unlock()
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
n.sessions[s] = struct{}{}
|
||||
n.mu.Unlock()
|
||||
slog.Info("节点 agent登录", "agent", lg.AgentId, "remote", conn.RemoteAddr().String())
|
||||
|
||||
s.loop()
|
||||
n.detachSession(s)
|
||||
}
|
||||
|
||||
// detachSession 会话断开后:摘除会话,关闭其名下全部隧道
|
||||
func (n *Node) detachSession(s *Session) {
|
||||
n.mu.Lock()
|
||||
delete(n.sessions, s)
|
||||
var mine []*TunnelServer
|
||||
for id, t := range n.tunnels {
|
||||
if t.session == s {
|
||||
mine = append(mine, t)
|
||||
delete(n.tunnels, id)
|
||||
}
|
||||
}
|
||||
n.mu.Unlock()
|
||||
for _, t := range mine {
|
||||
t.close()
|
||||
}
|
||||
slog.Info("节点 agent会话结束", "agent", s.agentId, "关闭隧道", len(mine))
|
||||
}
|
||||
|
||||
// Kick 踢掉指定 agent 的全部会话(运维/测试用)。
|
||||
// 新版 agent 每条隧道一条会话,Kick 会断其所有隧道,agent 侧各自重连重注册。
|
||||
func (n *Node) Kick(agentId string) bool {
|
||||
n.mu.Lock()
|
||||
var targets []*Session
|
||||
for s := range n.sessions {
|
||||
if s.agentId == agentId {
|
||||
targets = append(targets, s)
|
||||
}
|
||||
}
|
||||
n.mu.Unlock()
|
||||
for _, s := range targets {
|
||||
s.close()
|
||||
}
|
||||
return len(targets) > 0
|
||||
}
|
||||
|
||||
// registerTunnel 为 agent 注册一条隧道并开始公网监听
|
||||
func (n *Node) registerTunnel(s *Session, t agent.Tunnel) (agent.Tunnel, error) {
|
||||
if t.Id == "" {
|
||||
return t, errors.New("隧道ID不能为空")
|
||||
}
|
||||
if t.TargetIp == "" || t.TargetPort <= 0 || t.TargetPort > 65535 {
|
||||
return t, fmt.Errorf("目标地址错误:%s", t.TargetAddr())
|
||||
}
|
||||
if t.ListenPort < 0 || t.ListenPort > 65535 {
|
||||
return t, fmt.Errorf("监听端口错误:%d", t.ListenPort)
|
||||
}
|
||||
// 凭据只用于登录,节点侧不留存也不回传(CmdTarget 不携带)
|
||||
t.NodeAddr, t.Token = "", ""
|
||||
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
if n.stopped {
|
||||
return t, errors.New("节点已停止")
|
||||
}
|
||||
// 同 ID 隧道已存在:同 agent 重登顶替(更新配置)或死会话残留可替换,
|
||||
// 其它 agent 的活隧道拒绝
|
||||
if old, ok := n.tunnels[t.Id]; ok {
|
||||
if old.session.agentId != s.agentId && !old.session.isDead() {
|
||||
return t, fmt.Errorf("隧道已存在:%s", t.Id)
|
||||
}
|
||||
old.close()
|
||||
old.session.close() // 顶替旧连接,避免旧 worker 与新 worker 争抢
|
||||
delete(n.tunnels, t.Id)
|
||||
slog.Info("节点 同agent隧道顶替", "id", t.Id, "agent", s.agentId)
|
||||
}
|
||||
|
||||
ts := &TunnelServer{tunnel: t, session: s}
|
||||
ts.touch()
|
||||
l, err := net.Listen("tcp", net.JoinHostPort(n.BindTunnel, strconv.Itoa(t.ListenPort)))
|
||||
if err != nil {
|
||||
return t, err
|
||||
}
|
||||
ts.listener = l
|
||||
_, portStr, _ := net.SplitHostPort(l.Addr().String())
|
||||
ts.tunnel.ListenPort, _ = strconv.Atoi(portStr)
|
||||
|
||||
n.tunnels[t.Id] = ts
|
||||
go ts.acceptLoop()
|
||||
slog.Info("节点 隧道注册", "id", t.Id, "listen", ts.tunnel.ListenPort,
|
||||
"target", t.TargetAddr(), "ttl", t.TTLSec)
|
||||
return ts.tunnel, nil
|
||||
}
|
||||
|
||||
// unregisterTunnel 删除隧道(关闭公网监听)
|
||||
func (n *Node) unregisterTunnel(id string) bool {
|
||||
n.mu.Lock()
|
||||
ts, ok := n.tunnels[id]
|
||||
if ok {
|
||||
delete(n.tunnels, id)
|
||||
}
|
||||
n.mu.Unlock()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
ts.close()
|
||||
slog.Info("节点 隧道删除", "id", id)
|
||||
return true
|
||||
}
|
||||
|
||||
func (n *Node) sweepLoop() {
|
||||
ticker := time.NewTicker(n.SweepInterval)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
if n.isStopped() {
|
||||
return
|
||||
}
|
||||
n.sweepOnce()
|
||||
}
|
||||
}
|
||||
|
||||
// sweepOnce 清扫 TTL 过期且空闲的隧道,并向所属 agent 推送关闭通知
|
||||
func (n *Node) sweepOnce() {
|
||||
now := time.Now()
|
||||
n.mu.Lock()
|
||||
var expired []*TunnelServer
|
||||
for id, ts := range n.tunnels {
|
||||
ttl := time.Duration(ts.tunnel.TTLSec) * time.Second
|
||||
if ttl > 0 && ts.connCount.Load() == 0 && now.Sub(ts.lastActiveTime()) > ttl {
|
||||
expired = append(expired, ts)
|
||||
delete(n.tunnels, id)
|
||||
}
|
||||
}
|
||||
n.mu.Unlock()
|
||||
for _, ts := range expired {
|
||||
ts.close()
|
||||
ts.session.pushClosed(ts.tunnel.Id)
|
||||
slog.Info("节点 隧道TTL过期删除", "id", ts.tunnel.Id)
|
||||
}
|
||||
}
|
||||
|
||||
func writeRet(conn net.Conn, cmd byte, sid uint32, ret agent.Ret) error {
|
||||
f := agent.NewFrame(agent.FrameVersion, cmd, sid)
|
||||
if err := f.Marshal(ret); err != nil {
|
||||
return err
|
||||
}
|
||||
return agent.WriteFrame(conn, f)
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"git.zeroonesoft.cn/golib/zonat/agent"
|
||||
|
||||
"github.com/xtaci/smux"
|
||||
)
|
||||
|
||||
// Session 一个 agent 连接的会话:smux 服务端 + 控制流处理。
|
||||
// 数据流由节点侧隧道监听主动 OpenStream;agent 主动打开的第一条流是控制流,
|
||||
// 用于注册/注销隧道与心跳。
|
||||
type Session struct {
|
||||
agentId string
|
||||
node *Node
|
||||
session *smux.Session
|
||||
|
||||
control net.Conn // agent 打开的控制流
|
||||
controlMu sync.Mutex // 控制流写锁(handleControl 与 pushClosed 并发写)
|
||||
dead atomic.Bool
|
||||
}
|
||||
|
||||
func (s *Session) isDead() bool { return s.dead.Load() }
|
||||
|
||||
func (s *Session) close() {
|
||||
s.dead.Store(true)
|
||||
s.session.Close()
|
||||
}
|
||||
|
||||
// OpenStream 建立一条到 agent 的数据流
|
||||
func (s *Session) OpenStream() (net.Conn, error) {
|
||||
return s.session.OpenStream()
|
||||
}
|
||||
|
||||
// loop 接受 agent 打开的控制流,直到会话结束
|
||||
func (s *Session) loop() {
|
||||
for {
|
||||
stream, err := s.session.AcceptStream()
|
||||
if err != nil {
|
||||
s.close()
|
||||
return
|
||||
}
|
||||
// agent 打开的第一条流是控制流,pushClosed 依赖它;
|
||||
// 赋值与写入共用 controlMu,避免与推送协程竞争
|
||||
s.controlMu.Lock()
|
||||
if s.control == nil {
|
||||
s.control = stream
|
||||
}
|
||||
s.controlMu.Unlock()
|
||||
go s.handleControl(stream)
|
||||
}
|
||||
}
|
||||
|
||||
// handleControl 处理控制流上的帧:注册/注销隧道、心跳。
|
||||
// 单条控制流内的帧顺序处理;应答与节点推送共用 controlMu 串行写入。
|
||||
func (s *Session) handleControl(stream net.Conn) {
|
||||
defer stream.Close()
|
||||
for {
|
||||
f, err := agent.ReadFrame(stream)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch f.Cmd() {
|
||||
case agent.CmdRegisterTunnel:
|
||||
t := agent.Tunnel{}
|
||||
ret := agent.Ret{Code: 1, Msg: "注册失败"}
|
||||
if err := f.Unmarshal(&t); err != nil {
|
||||
ret.Msg = err.Error()
|
||||
} else if nt, err := s.node.registerTunnel(s, t); err != nil {
|
||||
ret.Msg = err.Error()
|
||||
} else {
|
||||
ret = agent.Ret{Code: 0, Msg: "注册成功", Port: nt.ListenPort}
|
||||
}
|
||||
s.reply(stream, f, ret)
|
||||
case agent.CmdUnregisterTunnel:
|
||||
t := agent.Tunnel{}
|
||||
ret := agent.Ret{Code: 1, Msg: "注销失败"}
|
||||
if err := f.Unmarshal(&t); err != nil {
|
||||
ret.Msg = err.Error()
|
||||
} else if s.node.unregisterTunnel(t.Id) {
|
||||
ret = agent.Ret{Code: 0, Msg: "注销成功"}
|
||||
} else {
|
||||
ret.Msg = "隧道不存在"
|
||||
}
|
||||
s.reply(stream, f, ret)
|
||||
case agent.CmdPing:
|
||||
s.writeControl(func() error {
|
||||
return agent.WriteFrame(stream, agent.NewFrame(agent.FrameVersion, agent.CmdPing, f.StreamID()))
|
||||
})
|
||||
case agent.CmdLogin, agent.CmdTarget, agent.CmdTunnelClosed:
|
||||
// 不该出现在 agent→节点方向,忽略
|
||||
default:
|
||||
s.reply(stream, f, agent.Ret{Code: 1, Msg: "未知命令"})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) reply(stream net.Conn, req agent.Frame, ret agent.Ret) {
|
||||
s.writeControl(func() error {
|
||||
f := agent.NewFrame(agent.FrameVersion, req.Cmd(), req.StreamID())
|
||||
if err := f.Marshal(ret); err != nil {
|
||||
return err
|
||||
}
|
||||
return agent.WriteFrame(stream, f)
|
||||
})
|
||||
}
|
||||
|
||||
// pushClosed 节点主动通知 agent:隧道已被删除(TTL 过期等)
|
||||
func (s *Session) pushClosed(tunnelId string) {
|
||||
if s.control == nil {
|
||||
return
|
||||
}
|
||||
s.writeControl(func() error {
|
||||
f := agent.NewFrame(agent.FrameVersion, agent.CmdTunnelClosed, 0)
|
||||
if err := f.Marshal(agent.Tunnel{Id: tunnelId}); err != nil {
|
||||
return err
|
||||
}
|
||||
return agent.WriteFrame(s.control, f)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Session) writeControl(fn func() error) {
|
||||
s.controlMu.Lock()
|
||||
defer s.controlMu.Unlock()
|
||||
if s.dead.Load() {
|
||||
return
|
||||
}
|
||||
// 写失败意味着会话已死,由读循环感知并收尾
|
||||
_ = fn()
|
||||
}
|
||||
|
||||
func smuxServer(conn net.Conn) (*smux.Session, error) {
|
||||
return smux.Server(conn, agent.SmuxConfig())
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"git.zeroonesoft.cn/golib/zonat/agent"
|
||||
)
|
||||
|
||||
// TunnelServer 一条已注册隧道的公网侧监听。
|
||||
// 纯 TCP 字节管道:无协议嗅探、无 HTTP 代理、无证书,任何 TCP 协议原样通过。
|
||||
type TunnelServer struct {
|
||||
tunnel agent.Tunnel
|
||||
session *Session
|
||||
listener net.Listener
|
||||
|
||||
connCount atomic.Int64
|
||||
lastActive atomic.Int64 // unix nano
|
||||
closed atomic.Bool
|
||||
}
|
||||
|
||||
// Port 公网监听端口
|
||||
func (t *TunnelServer) Port() int { return t.tunnel.ListenPort }
|
||||
|
||||
func (t *TunnelServer) touch() { t.lastActive.Store(time.Now().UnixNano()) }
|
||||
|
||||
func (t *TunnelServer) lastActiveTime() time.Time {
|
||||
return time.Unix(0, t.lastActive.Load())
|
||||
}
|
||||
|
||||
func (t *TunnelServer) acceptLoop() {
|
||||
for {
|
||||
conn, err := t.listener.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if t.closed.Load() {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
go t.handleConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
// handleConn 公网连接 → smux 新流 → 首帧告知目标 → agent 回执后双向管道。
|
||||
// 与 zonat 多路复用路径一致,仅去掉协议嗅探与限流。
|
||||
func (t *TunnelServer) handleConn(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
if t.session.isDead() {
|
||||
return
|
||||
}
|
||||
stream, err := t.session.OpenStream()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
tf := agent.NewFrame(agent.FrameVersion, agent.CmdTarget, 0)
|
||||
if err := tf.Marshal(t.tunnel); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agent.WriteFrame(stream, tf); err != nil {
|
||||
return
|
||||
}
|
||||
rf, err := agent.ReadFrame(stream)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ret := agent.Ret{}
|
||||
if err := rf.Unmarshal(&ret); err != nil || ret.Code != 0 {
|
||||
slog.Warn("节点 agent拨号失败", "id", t.tunnel.Id, "msg", ret.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
// 公网侧 TCP 保活(smux 流自身由会话级 keepalive 保护)
|
||||
if tc, ok := conn.(*net.TCPConn); ok {
|
||||
tc.SetKeepAlive(true)
|
||||
tc.SetKeepAlivePeriod(5 * time.Second)
|
||||
}
|
||||
t.connCount.Add(1)
|
||||
t.touch()
|
||||
defer func() {
|
||||
t.connCount.Add(-1)
|
||||
t.touch()
|
||||
}()
|
||||
|
||||
pipe(conn, stream)
|
||||
}
|
||||
|
||||
func (t *TunnelServer) close() {
|
||||
if t.closed.CompareAndSwap(false, true) && t.listener != nil {
|
||||
t.listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// pipe 双向拷贝,任一方向结束即关闭两端
|
||||
func pipe(a, b net.Conn) {
|
||||
go func() {
|
||||
io.Copy(b, a)
|
||||
b.Close()
|
||||
}()
|
||||
io.Copy(a, b)
|
||||
a.Close()
|
||||
}
|
||||
Reference in New Issue
Block a user