Базовый Readme, понятные переменные окружения

This commit is contained in:
set
2026-03-17 14:28:15 +02:00
parent e703ac22e6
commit 3c810407db
5 changed files with 84 additions and 31 deletions

View File

@@ -1,2 +1,15 @@
### SFU server ### SFU сервер
Сервер для организации видеоконференций на основе WebRTC. Написан на Go и использует библиотеку Pion WebRTC. SFU - Selective Forwarding Unit - это тип медиасервера, который принимает медиа-потоки от участников видеоконференции и пересылает их другим участникам без декодирования и повторного кодирования. Это позволяет снизить нагрузку на сервер и улучшить качество видео для всех участников. Сейчас G365SFU просто пересылает RTP пакеты между участниками, не обрабатывая их содержимое (потому, что оно зашифровано). В будущем планируется добавить возможность обработки слоев улучшения.
### TURN сервер
TURN - Traversal Using Relays around NAT - это протокол, который позволяет устройствам за NAT (Network Address Translation) или брандмауэром устанавливать связь с другими устройствами в интернете. TURN серверы используются для ретрансляции медиа-трафика между участниками видеоконференции, когда прямое соединение между ними невозможно из-за ограничений сети. В G365SFU используется встроенный TURN сервер, который можно включить с помощью переменной окружения `TURN_ALLOW=true`. Он будет слушать на порту 3478 и использовать диапазон портов от 40000 до 50000 для ретрансляции трафика. Параметры сервера, такие как публичный IP, имя пользователя и пароль, также настраиваются через переменные окружения. TURN сервер обеспечивает надежную связь между участниками звонка, даже если они находятся за NAT.
# Установка
Для начала, нам необходимо открыть порты 30000-39999 для SFU и 40000-50000 для TURN сервера (по умолчанию, если перенастраивается .env то нужно указать другие). Это можно сделать с помощью команды `ufw`:
```bash
sudo ufw allow 30000:39999/udp
sudo ufw allow 40000:50000/udp
sudo ufw allow 30000:39999/tcp
sudo ufw allow 40000:50000/tcp
sudo ufw allow 3478/tcp
```

View File

@@ -9,6 +9,7 @@ import (
"g365sfu/socket" "g365sfu/socket"
connection "g365sfu/socket/struct" connection "g365sfu/socket/struct"
"g365sfu/turn" "g365sfu/turn"
"g365sfu/utils"
"net/http" "net/http"
"os" "os"
@@ -32,18 +33,26 @@ func Bootstrap() {
sfu.OnServerOffer = OnServerOffer sfu.OnServerOffer = OnServerOffer
sfu.OnRoomDelete = OnRoomDelete sfu.OnRoomDelete = OnRoomDelete
sfu.OnPeerDisconnected = OnPeerDisconnected sfu.OnPeerDisconnected = OnPeerDisconnected
turnServer, err := turn.Start(turn.Config{ if os.Getenv("TURN_ALLOW") == "true" {
ListenAddr: "0.0.0.0:3478", turnServer, err := turn.Start(turn.Config{
PublicIP: os.Getenv("TURN_PUBLIC_IP"), ListenAddr: "0.0.0.0:3478",
Realm: "g365sfu", PublicIP: os.Getenv("TURN_PUBLIC_IP"),
Username: os.Getenv("TURN_USER"), Realm: "g365sfu",
Password: os.Getenv("TURN_PASS"), Username: os.Getenv("TURN_USER"),
}) Password: os.Getenv("TURN_PASS"),
if err != nil { MinRelayPort: uint16(utils.AtoiOrDefault(os.Getenv("TURN_PORT_RANGE_FROM"), 40000)),
logger.LogWarnMessage("TURN start failed: " + err.Error()) MaxRelayPort: uint16(utils.AtoiOrDefault(os.Getenv("TURN_PORT_RANGE_TO"), 50000)),
})
if err != nil {
logger.LogWarnMessage("error while starting TURN server: " + err.Error())
logger.LogInfoMessage("starting without TURN server, peer connections may fail if clients are behind symmetric NATs")
} else {
logger.LogInfoMessage("server TURN started at 0.0.0.0:3478")
defer turnServer.Close()
}
} else { } else {
logger.LogInfoMessage("TURN started on 0.0.0.0:3478") // TURN сервер выключен в конфиге, что может влиять на соединение некоторых пользователей
defer turnServer.Close() logger.LogInfoMessage("starting without TURN server, peer connections may fail if clients are behind symmetric NATs")
} }
logger.LogInfoMessage("server started at x.x.x.x:" + port) logger.LogInfoMessage("server started at x.x.x.x:" + port)
http.ListenAndServe(":"+port, nil) http.ListenAndServe(":"+port, nil)

View File

@@ -4,6 +4,7 @@ import (
"errors" "errors"
"g365sfu/logger" "g365sfu/logger"
connection "g365sfu/socket/struct" connection "g365sfu/socket/struct"
"g365sfu/utils"
"os" "os"
"github.com/pion/interceptor" "github.com/pion/interceptor"
@@ -34,12 +35,12 @@ var (
) )
func InitWebRTCEngines() { func InitWebRTCEngines() {
publicIP := os.Getenv("PUBLIC_IP") publicIP := os.Getenv("SFU_PUBLIC_IP")
fromPort := os.Getenv("PORT_RANGE_FROM") fromPort := utils.AtoiOrDefault(os.Getenv("SFU_PORT_RANGE_FROM"), 30000)
toPort := os.Getenv("PORT_RANGE_TO") toPort := utils.AtoiOrDefault(os.Getenv("SFU_PORT_RANGE_TO"), 39999)
if publicIP == "" || fromPort == "" || toPort == "" { if publicIP == "" || fromPort == 0 || toPort == 0 {
// Если не указаны необходимые переменные окружения, логируем ошибку и завершаем процесс сервера // Если не указаны необходимые переменные окружения, логируем ошибку и завершаем процесс сервера
logger.LogErrorMessage("PUBLIC_IP, PORT_RANGE_FROM and PORT_RANGE_TO environment variables must be set") logger.LogErrorMessage("SFU_PUBLIC_IP, SFU_PORT_RANGE_FROM and SFU_PORT_RANGE_TO environment variables must be set")
os.Exit(-1) os.Exit(-1)
return return
} }
@@ -50,9 +51,9 @@ func InitWebRTCEngines() {
_ = webrtc.RegisterDefaultInterceptors(m, i) _ = webrtc.RegisterDefaultInterceptors(m, i)
se := webrtc.SettingEngine{} se := webrtc.SettingEngine{}
_ = se.SetEphemeralUDPPortRange(40000, 50000) _ = se.SetEphemeralUDPPortRange(uint16(fromPort), uint16(toPort))
if publicIP := os.Getenv("PUBLIC_IP"); publicIP != "" { if publicIP := os.Getenv("SFU_PUBLIC_IP"); publicIP != "" {
se.SetICEAddressRewriteRules(webrtc.ICEAddressRewriteRule{ se.SetICEAddressRewriteRules(webrtc.ICEAddressRewriteRule{
External: []string{publicIP}, External: []string{publicIP},
AsCandidateType: webrtc.ICECandidateTypeHost, AsCandidateType: webrtc.ICECandidateTypeHost,

View File

@@ -19,6 +19,25 @@ type Config struct {
Realm string Realm string
Username string Username string
Password string Password string
MinRelayPort uint16
MaxRelayPort uint16
}
func relayGen(ip net.IP, minPort, maxPort uint16) pionturn.RelayAddressGenerator {
if minPort != 0 && maxPort != 0 && minPort <= maxPort {
return &pionturn.RelayAddressGeneratorPortRange{
RelayAddress: ip,
Address: "0.0.0.0",
MinPort: minPort,
MaxPort: maxPort,
}
}
return &pionturn.RelayAddressGeneratorStatic{
RelayAddress: ip,
Address: "0.0.0.0",
}
} }
func Start(cfg Config) (*Server, error) { func Start(cfg Config) (*Server, error) {
@@ -38,6 +57,8 @@ func Start(cfg Config) (*Server, error) {
return nil, err return nil, err
} }
rg := relayGen(ip, cfg.MinRelayPort, cfg.MaxRelayPort)
srv, err := pionturn.NewServer(pionturn.ServerConfig{ srv, err := pionturn.NewServer(pionturn.ServerConfig{
Realm: cfg.Realm, Realm: cfg.Realm,
AuthHandler: func(username, realm string, srcAddr net.Addr) ([]byte, bool) { AuthHandler: func(username, realm string, srcAddr net.Addr) ([]byte, bool) {
@@ -48,23 +69,18 @@ func Start(cfg Config) (*Server, error) {
}, },
PacketConnConfigs: []pionturn.PacketConnConfig{ PacketConnConfigs: []pionturn.PacketConnConfig{
{ {
PacketConn: udpConn, PacketConn: udpConn,
RelayAddressGenerator: &pionturn.RelayAddressGeneratorStatic{ RelayAddressGenerator: rg,
RelayAddress: ip,
Address: "0.0.0.0",
},
}, },
}, },
ListenerConfigs: []pionturn.ListenerConfig{ ListenerConfigs: []pionturn.ListenerConfig{
{ {
Listener: tcpListener, Listener: tcpListener,
RelayAddressGenerator: &pionturn.RelayAddressGeneratorStatic{ RelayAddressGenerator: rg,
RelayAddress: ip,
Address: "0.0.0.0",
},
}, },
}, },
}) })
if err != nil { if err != nil {
_ = tcpListener.Close() _ = tcpListener.Close()
_ = udpConn.Close() _ = udpConn.Close()

View File

@@ -1,6 +1,9 @@
package utils package utils
import "math/rand" import (
"math/rand"
"strconv"
)
// Генерация случайной строки заданной длины // Генерация случайной строки заданной длины
func RandomString(n int) string { func RandomString(n int) string {
@@ -11,3 +14,14 @@ func RandomString(n int) string {
} }
return string(b) return string(b)
} }
func AtoiOrDefault(s string, defaultValue int) int {
if s == "" {
return defaultValue
}
n, err := strconv.Atoi(s)
if err != nil {
return defaultValue
}
return n
}