From 3c810407db033c072a075e8d6277c74a5d01bc60 Mon Sep 17 00:00:00 2001 From: set Date: Tue, 17 Mar 2026 14:28:15 +0200 Subject: [PATCH] =?UTF-8?q?=D0=91=D0=B0=D0=B7=D0=BE=D0=B2=D1=8B=D0=B9=20Re?= =?UTF-8?q?adme,=20=D0=BF=D0=BE=D0=BD=D1=8F=D1=82=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D0=BE=D0=BA=D1=80=D1=83=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 17 +++++++++++++++-- boot/boot.go | 31 ++++++++++++++++++++----------- sfu/sfu.go | 15 ++++++++------- turn/turn.go | 36 ++++++++++++++++++++++++++---------- utils/utils.go | 16 +++++++++++++++- 5 files changed, 84 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index f1934dc..66e401f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,15 @@ -### SFU server -Сервер для организации видеоконференций на основе WebRTC. Написан на Go и использует библиотеку Pion WebRTC. \ No newline at end of file +### SFU сервер +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 +``` \ No newline at end of file diff --git a/boot/boot.go b/boot/boot.go index 259a487..baa7bfd 100644 --- a/boot/boot.go +++ b/boot/boot.go @@ -9,6 +9,7 @@ import ( "g365sfu/socket" connection "g365sfu/socket/struct" "g365sfu/turn" + "g365sfu/utils" "net/http" "os" @@ -32,18 +33,26 @@ func Bootstrap() { sfu.OnServerOffer = OnServerOffer sfu.OnRoomDelete = OnRoomDelete sfu.OnPeerDisconnected = OnPeerDisconnected - turnServer, err := turn.Start(turn.Config{ - ListenAddr: "0.0.0.0:3478", - PublicIP: os.Getenv("TURN_PUBLIC_IP"), - Realm: "g365sfu", - Username: os.Getenv("TURN_USER"), - Password: os.Getenv("TURN_PASS"), - }) - if err != nil { - logger.LogWarnMessage("TURN start failed: " + err.Error()) + if os.Getenv("TURN_ALLOW") == "true" { + turnServer, err := turn.Start(turn.Config{ + ListenAddr: "0.0.0.0:3478", + PublicIP: os.Getenv("TURN_PUBLIC_IP"), + Realm: "g365sfu", + Username: os.Getenv("TURN_USER"), + Password: os.Getenv("TURN_PASS"), + MinRelayPort: uint16(utils.AtoiOrDefault(os.Getenv("TURN_PORT_RANGE_FROM"), 40000)), + 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 { - logger.LogInfoMessage("TURN started on 0.0.0.0:3478") - defer turnServer.Close() + // TURN сервер выключен в конфиге, что может влиять на соединение некоторых пользователей + 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) http.ListenAndServe(":"+port, nil) diff --git a/sfu/sfu.go b/sfu/sfu.go index d44617c..f50cfec 100644 --- a/sfu/sfu.go +++ b/sfu/sfu.go @@ -4,6 +4,7 @@ import ( "errors" "g365sfu/logger" connection "g365sfu/socket/struct" + "g365sfu/utils" "os" "github.com/pion/interceptor" @@ -34,12 +35,12 @@ var ( ) func InitWebRTCEngines() { - publicIP := os.Getenv("PUBLIC_IP") - fromPort := os.Getenv("PORT_RANGE_FROM") - toPort := os.Getenv("PORT_RANGE_TO") - if publicIP == "" || fromPort == "" || toPort == "" { + publicIP := os.Getenv("SFU_PUBLIC_IP") + fromPort := utils.AtoiOrDefault(os.Getenv("SFU_PORT_RANGE_FROM"), 30000) + toPort := utils.AtoiOrDefault(os.Getenv("SFU_PORT_RANGE_TO"), 39999) + 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) return } @@ -50,9 +51,9 @@ func InitWebRTCEngines() { _ = webrtc.RegisterDefaultInterceptors(m, i) 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{ External: []string{publicIP}, AsCandidateType: webrtc.ICECandidateTypeHost, diff --git a/turn/turn.go b/turn/turn.go index 3ad39f8..952bc08 100644 --- a/turn/turn.go +++ b/turn/turn.go @@ -19,6 +19,25 @@ type Config struct { Realm string Username 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) { @@ -38,6 +57,8 @@ func Start(cfg Config) (*Server, error) { return nil, err } + rg := relayGen(ip, cfg.MinRelayPort, cfg.MaxRelayPort) + srv, err := pionturn.NewServer(pionturn.ServerConfig{ Realm: cfg.Realm, AuthHandler: func(username, realm string, srcAddr net.Addr) ([]byte, bool) { @@ -48,23 +69,18 @@ func Start(cfg Config) (*Server, error) { }, PacketConnConfigs: []pionturn.PacketConnConfig{ { - PacketConn: udpConn, - RelayAddressGenerator: &pionturn.RelayAddressGeneratorStatic{ - RelayAddress: ip, - Address: "0.0.0.0", - }, + PacketConn: udpConn, + RelayAddressGenerator: rg, }, }, ListenerConfigs: []pionturn.ListenerConfig{ { - Listener: tcpListener, - RelayAddressGenerator: &pionturn.RelayAddressGeneratorStatic{ - RelayAddress: ip, - Address: "0.0.0.0", - }, + Listener: tcpListener, + RelayAddressGenerator: rg, }, }, }) + if err != nil { _ = tcpListener.Close() _ = udpConn.Close() diff --git a/utils/utils.go b/utils/utils.go index 787fc32..164091a 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,6 +1,9 @@ package utils -import "math/rand" +import ( + "math/rand" + "strconv" +) // Генерация случайной строки заданной длины func RandomString(n int) string { @@ -11,3 +14,14 @@ func RandomString(n int) string { } 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 +}