121 lines
3.5 KiB
Go
121 lines
3.5 KiB
Go
/**
|
||
* 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))
|
||
}
|