Files
nl-im-service/internal/turnserver/server.go
2025-12-02 21:00:26 +08:00

121 lines
3.5 KiB
Go
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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))
}