0d84b07f68
CI / docker (push) Successful in 1m58s
Go + Vue 管理台:Agent 隧道、域名反代、IP 安全、访客验证与监控大盘。 Gitea Actions CI 多架构镜像;纯 Go SQLite、无 CGO Docker 构建。 Co-authored-by: Cursor <cursoragent@cursor.com>
217 lines
4.3 KiB
Go
217 lines
4.3 KiB
Go
package proxy
|
|
|
|
import (
|
|
"log"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/wormhole/wormhole/internal/protocol"
|
|
"github.com/wormhole/wormhole/internal/store"
|
|
)
|
|
|
|
type tcpServer struct {
|
|
manager *Manager
|
|
tunnel *store.Tunnel
|
|
ln net.Listener
|
|
stop chan struct{}
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
func newTCPServer(m *Manager, t *store.Tunnel) Service {
|
|
return &tcpServer{
|
|
manager: m,
|
|
tunnel: t,
|
|
stop: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
func (s *tcpServer) ID() uint { return s.tunnel.ID }
|
|
|
|
func (s *tcpServer) Start() error {
|
|
addr := net.JoinHostPort("0.0.0.0", itoa(s.tunnel.ListenPort))
|
|
ln, err := net.Listen("tcp", addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.ln = ln
|
|
log.Printf("[proxy] tcp tunnel %d listening on %s -> %s", s.tunnel.ID, addr, s.tunnel.TargetAddr)
|
|
go s.acceptLoop()
|
|
return nil
|
|
}
|
|
|
|
func (s *tcpServer) acceptLoop() {
|
|
for {
|
|
conn, err := s.ln.Accept()
|
|
if err != nil {
|
|
select {
|
|
case <-s.stop:
|
|
return
|
|
default:
|
|
continue
|
|
}
|
|
}
|
|
s.wg.Add(1)
|
|
go func(c net.Conn) {
|
|
defer s.wg.Done()
|
|
s.manager.handleTCPConnection(s.tunnel, c)
|
|
}(conn)
|
|
}
|
|
}
|
|
|
|
func (s *tcpServer) Stop() error {
|
|
close(s.stop)
|
|
if s.ln != nil {
|
|
_ = s.ln.Close()
|
|
}
|
|
s.wg.Wait()
|
|
return nil
|
|
}
|
|
|
|
type udpSession struct {
|
|
stream net.Conn
|
|
last time.Time
|
|
}
|
|
|
|
type udpServer struct {
|
|
manager *Manager
|
|
tunnel *store.Tunnel
|
|
conn *net.UDPConn
|
|
stop chan struct{}
|
|
mu sync.Mutex
|
|
sessions map[string]*udpSession
|
|
}
|
|
|
|
func newUDPServer(m *Manager, t *store.Tunnel) Service {
|
|
return &udpServer{
|
|
manager: m,
|
|
tunnel: t,
|
|
stop: make(chan struct{}),
|
|
sessions: make(map[string]*udpSession),
|
|
}
|
|
}
|
|
|
|
func (s *udpServer) ID() uint { return s.tunnel.ID }
|
|
|
|
func (s *udpServer) Start() error {
|
|
addr := net.JoinHostPort("0.0.0.0", itoa(s.tunnel.ListenPort))
|
|
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
conn, err := net.ListenUDP("udp", udpAddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.conn = conn
|
|
log.Printf("[proxy] udp tunnel %d listening on %s -> %s", s.tunnel.ID, addr, s.tunnel.TargetAddr)
|
|
go s.readLoop()
|
|
go s.sweeper()
|
|
return nil
|
|
}
|
|
|
|
func (s *udpServer) readLoop() {
|
|
buf := make([]byte, 65535)
|
|
for {
|
|
n, remote, err := s.conn.ReadFromUDP(buf)
|
|
if err != nil {
|
|
select {
|
|
case <-s.stop:
|
|
return
|
|
default:
|
|
continue
|
|
}
|
|
}
|
|
ip := remote.IP.String()
|
|
s.manager.metrics.RecordRequestIP("tunnel", s.tunnel.ID, ip, ip)
|
|
if !s.manager.security.Allow(ip, s.tunnel.ClientID, "tunnel", s.tunnel.ID) {
|
|
continue
|
|
}
|
|
key := remote.String()
|
|
s.mu.Lock()
|
|
sess, ok := s.sessions[key]
|
|
if !ok {
|
|
sess = s.createSession(remote)
|
|
if sess == nil {
|
|
s.mu.Unlock()
|
|
continue
|
|
}
|
|
s.sessions[key] = sess
|
|
go s.runSession(key, sess, remote)
|
|
}
|
|
sess.last = time.Now()
|
|
stream := sess.stream
|
|
s.mu.Unlock()
|
|
_, _ = stream.Write(buf[:n])
|
|
}
|
|
}
|
|
|
|
func (s *udpServer) createSession(remote *net.UDPAddr) *udpSession {
|
|
if !s.manager.bridge.IsOnline(s.tunnel.ClientID) {
|
|
return nil
|
|
}
|
|
mode, _ := resolveProxyConfig(s.manager.store, s.tunnel.ClientID, s.tunnel.IPForwardMode, s.tunnel.CustomHeaders)
|
|
meta := &protocol.LinkMeta{
|
|
ConnType: protocol.ConnTypeUDP,
|
|
Target: s.tunnel.TargetAddr,
|
|
RemoteAddr: forwardIPForTCP(remote.String(), mode),
|
|
}
|
|
stream, err := s.manager.bridge.OpenStream(s.tunnel.ClientID, meta)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return &udpSession{stream: stream, last: time.Now()}
|
|
}
|
|
|
|
func (s *udpServer) runSession(key string, sess *udpSession, remote *net.UDPAddr) {
|
|
buf := make([]byte, 65535)
|
|
for {
|
|
n, err := sess.stream.Read(buf)
|
|
if err != nil {
|
|
break
|
|
}
|
|
_, _ = s.conn.WriteToUDP(buf[:n], remote)
|
|
inlet := int64(n)
|
|
s.manager.metrics.AddFlow("tunnel", s.tunnel.ID, s.tunnel.ClientID, inlet, 0)
|
|
}
|
|
s.mu.Lock()
|
|
delete(s.sessions, key)
|
|
s.mu.Unlock()
|
|
_ = sess.stream.Close()
|
|
}
|
|
|
|
func (s *udpServer) sweeper() {
|
|
ticker := time.NewTicker(time.Minute)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
now := time.Now()
|
|
s.mu.Lock()
|
|
for k, sess := range s.sessions {
|
|
if now.Sub(sess.last) > 120*time.Second {
|
|
_ = sess.stream.Close()
|
|
delete(s.sessions, k)
|
|
}
|
|
}
|
|
s.mu.Unlock()
|
|
case <-s.stop:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *udpServer) Stop() error {
|
|
close(s.stop)
|
|
if s.conn != nil {
|
|
_ = s.conn.Close()
|
|
}
|
|
s.mu.Lock()
|
|
for _, sess := range s.sessions {
|
|
_ = sess.stream.Close()
|
|
}
|
|
s.sessions = make(map[string]*udpSession)
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|