Files
zonat/cmd/agent/main.go
T

140 lines
4.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
}