Initial commit

This commit is contained in:
2026-06-30 15:14:37 +08:00
commit 15dab96872
311 changed files with 95639 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
package connect
import (
"fmt"
"net"
"time"
"github.com/pion/stun"
"go.uber.org/zap"
)
// STUNClient STUN 客户端 - 用于 NAT 探测和候选地址采集
type STUNClient struct {
servers []string
logger *zap.Logger
timeout time.Duration
}
// NewSTUNClient 创建 STUN 客户端
func NewSTUNClient(servers []string, logger *zap.Logger) *STUNClient {
return &STUNClient{
servers: servers,
logger: logger,
timeout: 5 * time.Second,
}
}
// DiscoverAddress 发现外部地址(通过单个 STUN 服务器)
func (c *STUNClient) DiscoverAddress(server string) (*net.UDPAddr, error) {
host, port, err := net.SplitHostPort(server)
if err != nil {
return nil, fmt.Errorf("STUN 服务器地址格式错误:%w", err)
}
udpAddr, err := net.ResolveUDPAddr("udp4", net.JoinHostPort(host, port))
if err != nil {
return nil, fmt.Errorf("解析 UDP 地址失败:%w", err)
}
conn, err := net.DialUDP("udp4", nil, udpAddr)
if err != nil {
return nil, fmt.Errorf("连接 STUN 服务器失败:%w", err)
}
defer conn.Close()
conn.SetDeadline(time.Now().Add(c.timeout))
// 构建 STUN Binding Request
msg, err := stun.Build(stun.BindingRequest, stun.TransactionID)
if err != nil {
return nil, fmt.Errorf("构建 STUN 请求失败:%w", err)
}
// 发送请求
if _, err := conn.Write(msg.Raw); err != nil {
return nil, fmt.Errorf("发送 STUN 请求失败:%w", err)
}
// 读取响应
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
return nil, fmt.Errorf("读取 STUN 响应失败:%w", err)
}
// 解析响应
res := &stun.Message{Raw: buf[:n]}
if err := res.Decode(); err != nil {
return nil, fmt.Errorf("解码 STUN 响应失败:%w", err)
}
// 提取 XOR-MAPPED-ADDRESS
var xorAddr stun.XORMappedAddress
if err := xorAddr.GetFrom(res); err != nil {
return nil, fmt.Errorf("提取外部地址失败:%w", err)
}
c.logger.Debug("STUN 查询成功",
zap.String("server", server),
zap.String("external_addr", xorAddr.String()))
return &net.UDPAddr{
IP: xorAddr.IP,
Port: xorAddr.Port,
}, nil
}
// CollectCandidates 收集候选地址(通过多个 STUN 服务器)
func (c *STUNClient) CollectCandidates() []string {
var candidates []string
for _, server := range c.servers {
addr, err := c.DiscoverAddress(server)
if err != nil {
c.logger.Debug("STUN 服务器查询失败",
zap.String("server", server),
zap.Error(err))
continue
}
candidates = append(candidates, addr.String())
c.logger.Debug("收集到候选地址",
zap.String("server", server),
zap.String("candidate", addr.String()))
}
return candidates
}
// GetExternalIP 获取外部 IP(兼容旧 API)
func (c *STUNClient) GetExternalIP() (string, error) {
for _, server := range c.servers {
addr, err := c.DiscoverAddress(server)
if err == nil {
return addr.IP.String(), nil
}
}
return "", fmt.Errorf("所有 STUN 服务器均查询失败")
}