140 lines
4.1 KiB
Go
140 lines
4.1 KiB
Go
// zonat 被控端入口:跑在内网机器上,按隧道定义登录各自节点并注册。
|
||
// 每条隧道自带节点地址与登录令牌(JWT),可分布在多个不同节点。
|
||
// 隧道通过 -tunnel 参数下发(后续由 capricorn cloud 经 WS 下发,见分析文档 P1):
|
||
//
|
||
// -tunnel id=web1,target=127.0.0.1:3000,node=10.0.0.2:5212,token=<jwt>,port=0,ttl=0
|
||
// -tunnel id=vnc1,target=127.0.0.1:5900,ttl=300 // node/token 取 -node/-token 缺省值
|
||
package main
|
||
|
||
import (
|
||
"flag"
|
||
"fmt"
|
||
"log/slog"
|
||
"net"
|
||
"os"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"git.zeroonesoft.cn/golib/zonat/agent"
|
||
)
|
||
|
||
// slogLogger 把 slog 适配为 agent.Logger 接口(独立二进制用;
|
||
// 宿主项目若是 logrus 可直接注入 logrus.StandardLogger())
|
||
type slogLogger struct{ l *slog.Logger }
|
||
|
||
func (s slogLogger) Debugf(format string, args ...any) { s.l.Debug(fmt.Sprintf(format, args...)) }
|
||
func (s slogLogger) Infof(format string, args ...any) { s.l.Info(fmt.Sprintf(format, args...)) }
|
||
func (s slogLogger) Warnf(format string, args ...any) { s.l.Warn(fmt.Sprintf(format, args...)) }
|
||
func (s slogLogger) Errorf(format string, args ...any) { s.l.Error(fmt.Sprintf(format, args...)) }
|
||
|
||
func main() {
|
||
nodeAddr := flag.String("node", "127.0.0.1:5212", "缺省节点桥接地址(隧道未带 node= 时使用)")
|
||
token := flag.String("token", "", "缺省登录令牌/JWT(隧道未带 token= 时使用)")
|
||
id := flag.String("id", "", "agent 唯一标识(默认取主机名)")
|
||
backoff := flag.Duration("backoff", 5*time.Second, "永久隧道重连退避")
|
||
ping := flag.Duration("ping", 30*time.Second, "控制流心跳间隔")
|
||
var tunnels sliceFlag
|
||
flag.Var(&tunnels, "tunnel", "隧道定义 id=..,target=ip:port[,node=addr][,token=jwt][,port=0][,ttl=0],可重复")
|
||
flag.Parse()
|
||
|
||
if *id == "" {
|
||
host, err := os.Hostname()
|
||
if err != nil {
|
||
slog.Error("获取主机名失败", "err", err.Error())
|
||
os.Exit(1)
|
||
}
|
||
*id = host
|
||
}
|
||
|
||
a := agent.New(*id)
|
||
a.Logger = slogLogger{slog.Default()}
|
||
a.Backoff = *backoff
|
||
a.PingEvery = *ping
|
||
a.OnTunnelPort = func(tunnelId string, port int) {
|
||
if port > 0 {
|
||
slog.Info("隧道端口", "id", tunnelId, "listen", port)
|
||
}
|
||
}
|
||
for _, spec := range tunnels {
|
||
t, err := parseTunnel(spec, *nodeAddr, *token)
|
||
if err != nil {
|
||
slog.Error("隧道参数错误", "spec", spec, "err", err.Error())
|
||
os.Exit(1)
|
||
}
|
||
if _, err := a.RegisterTunnel(t); err != nil {
|
||
slog.Warn("注册隧道失败(永久隧道会在后台持续重试)", "id", t.Id, "err", err.Error())
|
||
}
|
||
}
|
||
|
||
if err := a.Run(); err != nil {
|
||
slog.Error("agent 退出", "err", err.Error())
|
||
os.Exit(1)
|
||
}
|
||
}
|
||
|
||
// parseTunnel 解析一条隧道定义,node/token 未带时回落到全局缺省值
|
||
func parseTunnel(spec, defNode, defToken string) (agent.Tunnel, error) {
|
||
t := agent.Tunnel{NodeAddr: defNode, Token: defToken}
|
||
for _, kv := range strings.Split(spec, ",") {
|
||
parts := strings.SplitN(kv, "=", 2)
|
||
if len(parts) != 2 {
|
||
return t, errBad("键值对缺少=:" + kv)
|
||
}
|
||
switch parts[0] {
|
||
case "id":
|
||
t.Id = parts[1]
|
||
case "node":
|
||
t.NodeAddr = parts[1]
|
||
case "token":
|
||
t.Token = parts[1]
|
||
case "target":
|
||
host, portStr, err := net.SplitHostPort(parts[1])
|
||
if err != nil {
|
||
return t, err
|
||
}
|
||
port, err := strconv.Atoi(portStr)
|
||
if err != nil {
|
||
return t, err
|
||
}
|
||
t.TargetIp, t.TargetPort = host, port
|
||
case "port":
|
||
v, err := strconv.Atoi(parts[1])
|
||
if err != nil {
|
||
return t, err
|
||
}
|
||
t.ListenPort = v
|
||
case "ttl":
|
||
v, err := strconv.Atoi(parts[1])
|
||
if err != nil {
|
||
return t, err
|
||
}
|
||
t.TTLSec = v
|
||
default:
|
||
return t, errBad("未知键:" + parts[0])
|
||
}
|
||
}
|
||
if t.Id == "" || t.TargetIp == "" || t.TargetPort == 0 {
|
||
return t, errBad("缺少 id 或 target")
|
||
}
|
||
if t.NodeAddr == "" || t.Token == "" {
|
||
return t, errBad("缺少 node 或 token(登录令牌/JWT)")
|
||
}
|
||
return t, nil
|
||
}
|
||
|
||
func errBad(msg string) error { return &badSpec{msg} }
|
||
|
||
type badSpec struct{ msg string }
|
||
|
||
func (e *badSpec) Error() string { return e.msg }
|
||
|
||
type sliceFlag []string
|
||
|
||
func (f *sliceFlag) String() string { return strings.Join(*f, " ") }
|
||
|
||
func (f *sliceFlag) Set(v string) error {
|
||
*f = append(*f, v)
|
||
return nil
|
||
}
|