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
+139
View File
@@ -0,0 +1,139 @@
// 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
}
+25
View File
@@ -0,0 +1,25 @@
// 测试目标:HTTP 服务,验证纯 TCP 隧道承载 HTTP(webshell/文件服务场景)。
package main
import (
"flag"
"io"
"log"
"net/http"
)
func main() {
addr := flag.String("addr", "127.0.0.1:9001", "监听地址")
flag.Parse()
mux := http.NewServeMux()
mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"hello":"git.zeroonesoft.cn/golib/zonat","via":"tcp-tunnel"}`))
})
mux.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {
io.Copy(w, r.Body)
})
log.Println("http-demo 监听", *addr)
log.Fatal(http.ListenAndServe(*addr, mux))
}
+62
View File
@@ -0,0 +1,62 @@
// zonat 节点入口:TCP 桥接监听 + 隧道注册表 + 纯 TCP 隧道监听。
// 无管理 API、无数据库;隧道由 agent 经桥接控制流注册。
// agent 登录用 JWT 校验(HS256);token 正式部署由 cloud 签发,
// 手工/测试场景可用 -print-token 现场签发。
package main
import (
"flag"
"fmt"
"log/slog"
"os"
"os/signal"
"syscall"
"time"
"git.zeroonesoft.cn/golib/zonat/internal/node"
)
func main() {
addr := flag.String("addr", ":5212", "桥接监听地址")
tunnelBind := flag.String("tunnel-bind", "0.0.0.0", "隧道监听绑定地址")
jwtSecret := flag.String("jwt-secret", "", "agent 登录 JWT 校验密钥(HS256)")
sweep := flag.Duration("sweep", 30*time.Second, "TTL 清扫间隔")
// 便捷签发:node -jwt-secret s3cret -print-token -agent a1 -ttl 24h
printToken := flag.Bool("print-token", false, "用 -jwt-secret 签发一张 agent 登录 token 后退出")
tokenAgent := flag.String("agent", "", "print-token 时写入 agentId claim")
tokenTTL := flag.Duration("ttl", 24*time.Hour, "print-token 签发的有效期")
flag.Parse()
if *printToken {
if *jwtSecret == "" || *tokenTTL <= 0 {
slog.Error("print-token 需要 -jwt-secret 与正的 -ttl")
os.Exit(1)
}
tok, err := node.SignToken(*jwtSecret, *tokenAgent, *tokenTTL)
if err != nil {
slog.Error("签发失败", "err", err.Error())
os.Exit(1)
}
fmt.Println(tok)
return
}
if *jwtSecret == "" {
slog.Error("缺少 -jwt-secret(agent 登录 JWT 校验密钥)")
os.Exit(1)
}
n := node.New()
n.JwtSecret = *jwtSecret
n.BindTunnel = *tunnelBind
n.SweepInterval = *sweep
if err := n.Start(*addr); err != nil {
slog.Error("节点启动失败", "err", err.Error())
os.Exit(1)
}
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
<-quit
n.Stop()
}
+30
View File
@@ -0,0 +1,30 @@
// 测试目标:TCP 回显服务,模拟 VNC/RDP/3000 等内网 TCP 目标。
package main
import (
"flag"
"io"
"log"
"net"
)
func main() {
addr := flag.String("addr", "127.0.0.1:9000", "监听地址")
flag.Parse()
l, err := net.Listen("tcp", *addr)
if err != nil {
log.Fatal(err)
}
log.Println("tcp-echo 监听", l.Addr().String())
for {
conn, err := l.Accept()
if err != nil {
log.Fatal(err)
}
go func(c net.Conn) {
defer c.Close()
io.Copy(c, c)
}(conn)
}
}