/** * package turnserver * 作用:基于 pion/turn 封装内置的 WebRTC 中继服务。 * 特性: * 1. 支持 UDP 和 TCP 协议。 * 2. 手动实现了长期凭证机制 (Long-Term Credential Mechanism) 进行鉴权。 * 3. 支持分布式部署,通过共享密钥验证 Token。 */ package turnserver import ( "crypto/hmac" "crypto/sha1" "encoding/base64" "fmt" "log" "net" "strconv" "time" "github.com/pion/turn/v2" "github.com/spf13/viper" ) /** * Start * 功能:初始化并启动 TURN 服务器。 * 注意:这是一个阻塞调用,通常建议在 goroutine 中运行。 */ func Start() { if !viper.GetBool("turn.enabled") { log.Println("ℹ️ [TURN] 服务已禁用") return } publicIP := viper.GetString("turn.public_ip") port := viper.GetInt("turn.listen_port") realm := viper.GetString("turn.realm") // 监听 UDP 端口 udpListener, err := net.ListenPacket("udp4", "0.0.0.0:"+strconv.Itoa(port)) if err != nil { log.Fatalf("❌ [TURN] UDP 监听失败: %v", err) } // 定义鉴权回调函数 // 每次客户端发起 Allocate 请求时触发 authHandler := func(username, realm string, srcAddr net.Addr) ([]byte, bool) { // 1. (可选) 验证 username 时效性 // username 格式: timestamp:userid // 生产环境可以解析 timestamp 检查是否过期 // 2. 根据共享密钥重新计算密码 (HMAC-SHA1) secret := viper.GetString("turn.shared_secret") password := generateHMACPassword(username, secret) // 3. 生成 HA1 Key // pion/turn 的 AuthHandler 需要返回 MD5(username:realm:password) 作为 Key // 这是 STUN/TURN 标准的完整性校验要求 key := turn.GenerateAuthKey(username, realm, password) return key, true } // 创建 TURN Server 实例 s, err := turn.NewServer(turn.ServerConfig{ Realm: realm, AuthHandler: authHandler, // 配置中继地址生成器 (使用公网IP,否则客户端无法连接) PacketConnConfigs: []turn.PacketConnConfig{ { PacketConn: udpListener, RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{ RelayAddress: net.ParseIP(publicIP), Address: "0.0.0.0", }, }, }, }) if err != nil { log.Fatalf("❌ [TURN] 启动失败: %v", err) } log.Printf("🚀 [TURN] 服务已启动 | IP: %s | Port: %d | Realm: %s", publicIP, port, realm) // 阻塞保持服务运行 // 在 main.go 中,我们依靠主线程的 select 或 Web Server 阻塞,所以这里不需要 block // 但为了代码独立性,如果是独立进程,需要阻塞。这里因为是库函数,只需返回即可,Server 会在后台运行。 _ = s } /** * GenerateCredentials * 功能:为特定用户生成 TURN 服务的访问凭证。 * 算法:WebRTC 标准的 Long-Term Credential Mechanism (HMAC-SHA1)。 * @param userID 用户ID * @return username, password (credential) */ func GenerateCredentials(userID string) (string, string) { // Username = timestamp:userid (有效期 24 小时) timestamp := time.Now().Add(24 * time.Hour).Unix() username := fmt.Sprintf("%d:%s", timestamp, userID) secret := viper.GetString("turn.shared_secret") // 生成密码 password := generateHMACPassword(username, secret) return username, password } /** * generateHMACPassword * 功能:辅助函数,使用 HMAC-SHA1 算法生成密码 * 标准:Base64(HMAC-SHA1(secret, username)) */ func generateHMACPassword(username, secret string) string { mac := hmac.New(sha1.New, []byte(secret)) mac.Write([]byte(username)) return base64.StdEncoding.EncodeToString(mac.Sum(nil)) }