Files
Wormhole/internal/bridge/bridge.go
T
rose_cat707 0d84b07f68
CI / docker (push) Successful in 1m58s
feat: Wormhole 内网穿透与反向代理网关
Go + Vue 管理台:Agent 隧道、域名反代、IP 安全、访客验证与监控大盘。
Gitea Actions CI 多架构镜像;纯 Go SQLite、无 CGO Docker 构建。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 17:52:43 +08:00

319 lines
6.7 KiB
Go

package bridge
import (
"crypto/tls"
"fmt"
"io"
"log"
"net"
"sync"
"sync/atomic"
"time"
"github.com/xtaci/smux"
"github.com/wormhole/wormhole/internal/cache"
"github.com/wormhole/wormhole/internal/protocol"
"github.com/wormhole/wormhole/internal/store"
)
type AgentSession struct {
ClientID uint
Version string
conn net.Conn
smux *smux.Session
mu sync.Mutex
closed atomic.Bool
activeConns atomic.Int32
}
type Bridge struct {
cache *cache.Manager
store *store.Store
mu sync.RWMutex
agents map[uint]*AgentSession
listener net.Listener
}
func New(c *cache.Manager, st *store.Store) *Bridge {
return &Bridge{
cache: c,
store: st,
agents: make(map[uint]*AgentSession),
}
}
func (b *Bridge) Start(addr string, certFile, keyFile string) error {
var ln net.Listener
var err error
if certFile != "" && keyFile != "" {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return fmt.Errorf("load bridge tls cert: %w", err)
}
ln, err = tls.Listen("tcp", addr, &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
})
if err != nil {
return err
}
log.Printf("[bridge] listening on %s (TLS)", addr)
} else {
ln, err = net.Listen("tcp", addr)
if err != nil {
return err
}
log.Printf("[bridge] listening on %s (plain TCP, configure TLS cert for production)", addr)
}
b.listener = ln
go b.acceptLoop()
b.startWatchdog()
return nil
}
func (b *Bridge) Shutdown() error {
if b.listener != nil {
return b.listener.Close()
}
return nil
}
func (b *Bridge) acceptLoop() {
for {
conn, err := b.listener.Accept()
if err != nil {
return
}
go b.handleConn(conn)
}
}
func (b *Bridge) validateClient(client *store.Client) error {
if !client.Status {
return fmt.Errorf("client disabled")
}
if client.ExpireAt != nil && time.Now().After(*client.ExpireAt) {
return fmt.Errorf("client expired")
}
return nil
}
func (b *Bridge) handleConn(conn net.Conn) {
defer conn.Close()
SetTCPKeepAlive(conn)
msgType, body, err := protocol.ReadMessage(conn)
if err != nil {
log.Printf("[bridge] read register failed: %v", err)
return
}
if msgType != protocol.MsgRegister {
return
}
reg, err := protocol.ParseRegister(body)
if err != nil {
return
}
client, ok := b.cache.GetClientByKey(reg.VerifyKey)
if !ok {
_ = protocol.WriteMessage(conn, protocol.MsgRegisterAck, protocol.RegisterAckPayload{
OK: false,
Message: "invalid verify key",
})
return
}
if err := b.validateClient(client); err != nil {
_ = protocol.WriteMessage(conn, protocol.MsgRegisterAck, protocol.RegisterAckPayload{
OK: false,
Message: err.Error(),
})
return
}
if err := protocol.WriteMessage(conn, protocol.MsgRegisterAck, protocol.RegisterAckPayload{
OK: true,
ClientID: client.ID,
Message: "registered",
}); err != nil {
return
}
session, err := smux.Client(conn, smuxConfig())
if err != nil {
log.Printf("[bridge] smux client failed: %v", err)
return
}
agent := &AgentSession{
ClientID: client.ID,
Version: reg.Version,
conn: conn,
smux: session,
}
b.mu.Lock()
if old, exists := b.agents[client.ID]; exists {
old.Close()
}
b.agents[client.ID] = agent
b.mu.Unlock()
b.cache.SetClientOnline(client.ID, reg.Version)
log.Printf("[bridge] client %d online (version=%s)", client.ID, reg.Version)
b.controlLoop(agent)
b.mu.Lock()
delete(b.agents, client.ID)
b.mu.Unlock()
b.cache.SetClientOffline(client.ID)
log.Printf("[bridge] client %d offline", client.ID)
}
func (b *Bridge) controlLoop(agent *AgentSession) {
for {
stream, err := agent.smux.AcceptStream()
if err != nil {
agent.Close()
return
}
go b.handleControlStream(agent, stream)
}
}
func (b *Bridge) handleControlStream(agent *AgentSession, stream *smux.Stream) {
defer stream.Close()
msgType, body, err := protocol.ReadMessage(stream)
if err != nil {
return
}
switch msgType {
case protocol.MsgPing:
b.cache.TouchAgent(agent.ClientID)
_ = protocol.WriteMessage(stream, protocol.MsgPong, nil)
case protocol.MsgClose:
agent.Close()
}
_ = body
}
func (a *AgentSession) Close() {
if a.closed.Swap(true) {
return
}
if a.smux != nil {
_ = a.smux.Close()
}
if a.conn != nil {
_ = a.conn.Close()
}
}
func (b *Bridge) OpenStream(clientID uint, meta *protocol.LinkMeta) (net.Conn, error) {
client, err := b.store.GetClientByID(clientID)
if err != nil {
return nil, fmt.Errorf("client %d not found", clientID)
}
if err := b.validateClient(client); err != nil {
return nil, err
}
b.mu.RLock()
agent, ok := b.agents[clientID]
b.mu.RUnlock()
if !ok {
return nil, fmt.Errorf("client %d offline", clientID)
}
if agent.closed.Load() {
return nil, fmt.Errorf("client %d session closed", clientID)
}
if client.MaxConn > 0 && int(agent.activeConns.Load()) >= client.MaxConn {
return nil, fmt.Errorf("client %d max connections reached", clientID)
}
stream, err := agent.smux.OpenStream()
if err != nil {
return nil, err
}
if err := protocol.WriteMessage(stream, protocol.MsgOpenStream, meta); err != nil {
stream.Close()
return nil, err
}
msgType, _, err := protocol.ReadMessage(stream)
if err != nil {
stream.Close()
return nil, err
}
if msgType != protocol.MsgStreamReady {
stream.Close()
return nil, fmt.Errorf("stream not ready")
}
agent.activeConns.Add(1)
var wrapped net.Conn = &trackedConn{
Conn: stream,
onClose: func() { agent.activeConns.Add(-1) },
}
if client.RateLimit > 0 {
wrapped = newRateLimitedConn(wrapped, client.RateLimit*1024)
}
return wrapped, nil
}
func (b *Bridge) IsOnline(clientID uint) bool {
b.mu.RLock()
defer b.mu.RUnlock()
_, ok := b.agents[clientID]
return ok
}
func (b *Bridge) OnlineCount() int {
b.mu.RLock()
defer b.mu.RUnlock()
return len(b.agents)
}
type trackedConn struct {
net.Conn
onClose func()
once sync.Once
}
func (c *trackedConn) Close() error {
c.once.Do(c.onClose)
return c.Conn.Close()
}
func CopyBoth(a, b net.Conn) (inlet, export int64) {
var inVal, outVal int64
done := make(chan struct{}, 2)
go func() {
n, _ := io.Copy(b, a)
atomic.AddInt64(&outVal, n)
done <- struct{}{}
}()
go func() {
n, _ := io.Copy(a, b)
atomic.AddInt64(&inVal, n)
done <- struct{}{}
}()
<-done
<-done
return atomic.LoadInt64(&inVal), atomic.LoadInt64(&outVal)
}
func CopyBothIO(a io.ReadWriteCloser, b io.ReadWriteCloser) (inlet, export int64) {
var inVal, outVal int64
done := make(chan struct{}, 2)
go func() {
n, _ := io.Copy(b, a)
atomic.AddInt64(&outVal, n)
done <- struct{}{}
}()
go func() {
n, _ := io.Copy(a, b)
atomic.AddInt64(&inVal, n)
done <- struct{}{}
}()
<-done
<-done
return atomic.LoadInt64(&inVal), atomic.LoadInt64(&outVal)
}