mirror of
https://github.com/livekit/livekit.git
synced 2026-03-31 02:25:39 +00:00
Switching to using session specific TURN credentials instead of shared credentials per Room. Also eliminates need to load Room from Redis during TURN authentication
193 lines
5.7 KiB
Go
193 lines
5.7 KiB
Go
// Copyright 2023 LiveKit, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package service
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/jxskiss/base62"
|
|
"github.com/pion/turn/v2"
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/livekit/protocol/auth"
|
|
"github.com/livekit/protocol/livekit"
|
|
"github.com/livekit/protocol/logger"
|
|
"github.com/livekit/protocol/logger/pionlogger"
|
|
|
|
"github.com/livekit/livekit-server/pkg/config"
|
|
"github.com/livekit/livekit-server/pkg/telemetry"
|
|
"github.com/livekit/livekit-server/pkg/telemetry/prometheus"
|
|
)
|
|
|
|
const (
|
|
LivekitRealm = "livekit"
|
|
|
|
allocateRetries = 50
|
|
turnMinPort = 1024
|
|
turnMaxPort = 30000
|
|
)
|
|
|
|
func NewTurnServer(conf *config.Config, authHandler turn.AuthHandler, standalone bool) (*turn.Server, error) {
|
|
turnConf := conf.TURN
|
|
if !turnConf.Enabled {
|
|
return nil, nil
|
|
}
|
|
|
|
if turnConf.TLSPort <= 0 && turnConf.UDPPort <= 0 {
|
|
return nil, errors.New("invalid TURN ports")
|
|
}
|
|
|
|
serverConfig := turn.ServerConfig{
|
|
Realm: LivekitRealm,
|
|
AuthHandler: authHandler,
|
|
LoggerFactory: pionlogger.NewLoggerFactory(logger.GetLogger()),
|
|
}
|
|
var relayAddrGen turn.RelayAddressGenerator = &turn.RelayAddressGeneratorPortRange{
|
|
RelayAddress: net.ParseIP(conf.RTC.NodeIP),
|
|
Address: "0.0.0.0",
|
|
MinPort: turnConf.RelayPortRangeStart,
|
|
MaxPort: turnConf.RelayPortRangeEnd,
|
|
MaxRetries: allocateRetries,
|
|
}
|
|
if standalone {
|
|
relayAddrGen = telemetry.NewRelayAddressGenerator(relayAddrGen)
|
|
}
|
|
var logValues []interface{}
|
|
|
|
logValues = append(logValues, "turn.relay_range_start", turnConf.RelayPortRangeStart)
|
|
logValues = append(logValues, "turn.relay_range_end", turnConf.RelayPortRangeEnd)
|
|
|
|
if turnConf.TLSPort > 0 {
|
|
if turnConf.Domain == "" {
|
|
return nil, errors.New("TURN domain required")
|
|
}
|
|
|
|
if !IsValidDomain(turnConf.Domain) {
|
|
return nil, errors.New("TURN domain is not correct")
|
|
}
|
|
|
|
if !turnConf.ExternalTLS {
|
|
cert, err := tls.LoadX509KeyPair(turnConf.CertFile, turnConf.KeyFile)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "TURN tls cert required")
|
|
}
|
|
|
|
tlsListener, err := tls.Listen("tcp4", "0.0.0.0:"+strconv.Itoa(turnConf.TLSPort),
|
|
&tls.Config{
|
|
MinVersion: tls.VersionTLS12,
|
|
Certificates: []tls.Certificate{cert},
|
|
})
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not listen on TURN TCP port")
|
|
}
|
|
if standalone {
|
|
tlsListener = telemetry.NewListener(tlsListener)
|
|
}
|
|
|
|
listenerConfig := turn.ListenerConfig{
|
|
Listener: tlsListener,
|
|
RelayAddressGenerator: relayAddrGen,
|
|
}
|
|
serverConfig.ListenerConfigs = append(serverConfig.ListenerConfigs, listenerConfig)
|
|
} else {
|
|
tcpListener, err := net.Listen("tcp4", "0.0.0.0:"+strconv.Itoa(turnConf.TLSPort))
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not listen on TURN TCP port")
|
|
}
|
|
if standalone {
|
|
tcpListener = telemetry.NewListener(tcpListener)
|
|
}
|
|
|
|
listenerConfig := turn.ListenerConfig{
|
|
Listener: tcpListener,
|
|
RelayAddressGenerator: relayAddrGen,
|
|
}
|
|
serverConfig.ListenerConfigs = append(serverConfig.ListenerConfigs, listenerConfig)
|
|
}
|
|
logValues = append(logValues, "turn.portTLS", turnConf.TLSPort, "turn.externalTLS", turnConf.ExternalTLS)
|
|
}
|
|
|
|
if turnConf.UDPPort > 0 {
|
|
udpListener, err := net.ListenPacket("udp4", "0.0.0.0:"+strconv.Itoa(turnConf.UDPPort))
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not listen on TURN UDP port")
|
|
}
|
|
|
|
if standalone {
|
|
udpListener = telemetry.NewPacketConn(udpListener, prometheus.Incoming)
|
|
}
|
|
|
|
packetConfig := turn.PacketConnConfig{
|
|
PacketConn: udpListener,
|
|
RelayAddressGenerator: relayAddrGen,
|
|
}
|
|
serverConfig.PacketConnConfigs = append(serverConfig.PacketConnConfigs, packetConfig)
|
|
logValues = append(logValues, "turn.portUDP", turnConf.UDPPort)
|
|
}
|
|
|
|
logger.Infow("Starting TURN server", logValues...)
|
|
return turn.NewServer(serverConfig)
|
|
}
|
|
|
|
func getTURNAuthHandlerFunc(handler *TURNAuthHandler) turn.AuthHandler {
|
|
return handler.HandleAuth
|
|
}
|
|
|
|
type TURNAuthHandler struct {
|
|
keyProvider auth.KeyProvider
|
|
}
|
|
|
|
func NewTURNAuthHandler(keyProvider auth.KeyProvider) *TURNAuthHandler {
|
|
return &TURNAuthHandler{
|
|
keyProvider: keyProvider,
|
|
}
|
|
}
|
|
|
|
func (h *TURNAuthHandler) CreateUsername(apiKey string, pID livekit.ParticipantID) string {
|
|
return base62.EncodeToString([]byte(fmt.Sprintf("%s|%s", apiKey, pID)))
|
|
}
|
|
|
|
func (h *TURNAuthHandler) CreatePassword(apiKey string, pID livekit.ParticipantID) (string, error) {
|
|
secret := h.keyProvider.GetSecret(apiKey)
|
|
if secret == "" {
|
|
return "", ErrInvalidAPIKey
|
|
}
|
|
keyInput := fmt.Sprintf("%s|%s", secret, pID)
|
|
sum := sha256.Sum256([]byte(keyInput))
|
|
return base62.EncodeToString(sum[:]), nil
|
|
}
|
|
|
|
func (h *TURNAuthHandler) HandleAuth(username, realm string, srcAddr net.Addr) (key []byte, ok bool) {
|
|
decoded, err := base62.DecodeString(username)
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
parts := strings.Split(string(decoded), "|")
|
|
if len(parts) != 2 {
|
|
return nil, false
|
|
}
|
|
password, err := h.CreatePassword(parts[0], livekit.ParticipantID(parts[1]))
|
|
if err != nil {
|
|
logger.Warnw("could not create TURN password", err, "username", username)
|
|
return nil, false
|
|
}
|
|
return turn.GenerateAuthKey(username, LivekitRealm, password), true
|
|
}
|