diff --git a/cmd/server/commands.go b/cmd/server/commands.go index 79aeba78e..cb845f0f3 100644 --- a/cmd/server/commands.go +++ b/cmd/server/commands.go @@ -41,8 +41,7 @@ func printPorts(c *cli.Context) error { if conf.TURN.Enabled { udpPorts = append(udpPorts, fmt.Sprintf("%d-%d", conf.TURN.PortRangeStart, conf.TURN.PortRangeEnd)) - udpPorts = append(udpPorts, strconv.Itoa(conf.TURN.TCPPort)) - tcpPorts = append(tcpPorts, strconv.Itoa(conf.TURN.TCPPort)) + tcpPorts = append(tcpPorts, strconv.Itoa(conf.TURN.TLSPort)) } fmt.Println("TCP Ports") diff --git a/cmd/server/main.go b/cmd/server/main.go index 7089557e4..4906598d6 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -75,6 +75,16 @@ func main() { Name: "dev", Usage: "sets log-level to debug, and console formatter", }, + &cli.StringFlag{ + Name: "turn-cert", + Usage: "tls cert file for TURN server", + EnvVars: []string{"LIVEKIT_TURN_CERT"}, + }, + &cli.StringFlag{ + Name: "turn-key", + Usage: "tls key file for TURN server", + EnvVars: []string{"LIVEKIT_TURN_KEY"}, + }, }, Action: startServer, Commands: []*cli.Command{ diff --git a/config-sample.yaml b/config-sample.yaml index 2bb656890..ebcbfa812 100644 --- a/config-sample.yaml +++ b/config-sample.yaml @@ -65,3 +65,19 @@ keys: # update_interval: 500 # # to prevent speaker updates from too jumpy, smooth out values over N samples # smooth_samples: 5 + +# turn server +turn: + # Uses TLS. Requires cert and key pem files by either: + # - using turn.secretName if deploying with our helm chart, or + # - setting LIVEKIT_TURN_CERT and LIVEKIT_TURN_KEY env vars with file locations, or + # - using cert_file and key_file below + # defaults to false + enabled: false + # needs to match tls cert domain + domain: turn.myhost.com + # defaults to 3478 - if not using a load balancer, this must be set to 443 + tls_port: 3478 + # optional + # cert_file: /path/to/cert.pem + # key_file: /path/to/key.pem diff --git a/pkg/config/config.go b/pkg/config/config.go index 10c8e84b3..43caaa68e 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -71,8 +71,10 @@ type RedisConfig struct { type TURNConfig struct { Enabled bool `yaml:"enabled"` - TCPPort int `yaml:"tcp_port"` - UDPPort int `yaml:"udp_port"` + Domain string `yaml:"domain"` + CertFile string `yaml:"cert_file"` + KeyFile string `yaml:"key_file"` + TLSPort int `yaml:"tls_port"` PortRangeStart uint16 `yaml:"port_range_start"` PortRangeEnd uint16 `yaml:"port_range_end"` } @@ -108,8 +110,7 @@ func NewConfig(confString string) (*Config, error) { Redis: RedisConfig{}, TURN: TURNConfig{ Enabled: false, - TCPPort: 3478, - UDPPort: 3478, + TLSPort: 3478, PortRangeStart: 12000, PortRangeEnd: 14000, }, @@ -143,6 +144,12 @@ func (conf *Config) UpdateFromCLI(c *cli.Context) error { if c.IsSet("redis-password") { conf.Redis.Password = c.String("redis-password") } + if c.IsSet("turn-cert") { + conf.TURN.CertFile = c.String("turn-cert") + } + if c.IsSet("turn-key") { + conf.TURN.KeyFile = c.String("turn-key") + } // expand env vars in filenames file, err := homedir.Expand(os.ExpandEnv(conf.KeyFile)) if err != nil { diff --git a/pkg/service/roommanager.go b/pkg/service/roommanager.go index 139921b84..23673e499 100644 --- a/pkg/service/roommanager.go +++ b/pkg/service/roommanager.go @@ -480,21 +480,11 @@ func (r *RoomManager) iceServersForRoom(ri *livekit.Room) []*livekit.ICEServer { }) } if r.config.TURN.Enabled { - if r.config.TURN.TCPPort > 0 { - iceServers = append(iceServers, &livekit.ICEServer{ - Urls: []string{fmt.Sprintf("turn:%s:%d?transport=tcp", r.currentNode.Ip, r.config.TURN.TCPPort)}, - Username: ri.Name, - Credential: ri.TurnPassword, - }) - } - - if r.config.TURN.UDPPort > 0 { - iceServers = append(iceServers, &livekit.ICEServer{ - Urls: []string{fmt.Sprintf("turn:%s:%d?transport=udp", r.currentNode.Ip, r.config.TURN.UDPPort)}, - Username: ri.Name, - Credential: ri.TurnPassword, - }) - } + iceServers = append(iceServers, &livekit.ICEServer{ + Urls: []string{fmt.Sprintf("turns:%s:443?transport=tcp", r.config.TURN.Domain)}, + Username: ri.Name, + Credential: ri.TurnPassword, + }) } return iceServers } diff --git a/pkg/service/turn.go b/pkg/service/turn.go index 89ae75c01..3ce3ef547 100644 --- a/pkg/service/turn.go +++ b/pkg/service/turn.go @@ -1,6 +1,7 @@ package service import ( + "crypto/tls" "fmt" "net" "strconv" @@ -23,19 +24,32 @@ func NewTurnServer(conf *config.Config, roomStore RoomStore, node routing.LocalN if !turnConf.Enabled { return nil, nil } + if turnConf.Domain == "" { + return nil, errors.New("TURN domain required") + } + if turnConf.TLSPort == 0 { + return nil, errors.New("invalid TURN tcp port") + } + + 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") + } + serverConfig := turn.ServerConfig{ Realm: livekitRealm, AuthHandler: newTurnAuthHandler(roomStore), - } - - if turnConf.TCPPort > 0 { - tcpListener, err := net.Listen("tcp4", "0.0.0.0:"+strconv.Itoa(turnConf.TCPPort)) - if err != nil { - return nil, errors.Wrap(err, "could not listen on TURN TCP port") - } - serverConfig.ListenerConfigs = []turn.ListenerConfig{ + ListenerConfigs: []turn.ListenerConfig{ { - Listener: tcpListener, + Listener: tlsListener, RelayAddressGenerator: &turn.RelayAddressGeneratorPortRange{ RelayAddress: net.ParseIP(node.Ip), Address: "0.0.0.0", @@ -44,31 +58,11 @@ func NewTurnServer(conf *config.Config, roomStore RoomStore, node routing.LocalN MaxRetries: allocateRetries, }, }, - } - } - - 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") - } - serverConfig.PacketConnConfigs = []turn.PacketConnConfig{ - { - PacketConn: udpListener, - RelayAddressGenerator: &turn.RelayAddressGeneratorPortRange{ - RelayAddress: net.ParseIP(node.Ip), // Claim that we are listening on IP passed by user (This should be your Public IP) - Address: "0.0.0.0", // But actually be listening on every interface - MinPort: turnConf.PortRangeStart, - MaxPort: turnConf.PortRangeEnd, - MaxRetries: allocateRetries, - }, - }, - } + }, } logger.Infow("Starting TURN server", - "TCP port", turnConf.TCPPort, - "UDP port", turnConf.UDPPort, + "TCP port", turnConf.TLSPort, "portRange", fmt.Sprintf("%d-%d", turnConf.PortRangeStart, turnConf.PortRangeEnd)) return turn.NewServer(serverConfig) } diff --git a/test/turn_test.go b/test/turn_test.go index beb3c5cce..8e66e7369 100644 --- a/test/turn_test.go +++ b/test/turn_test.go @@ -9,14 +9,15 @@ import ( "github.com/pion/turn/v2" "github.com/stretchr/testify/require" + "github.com/livekit/protocol/utils" + "github.com/livekit/livekit-server/pkg/config" "github.com/livekit/livekit-server/pkg/routing" "github.com/livekit/livekit-server/pkg/service" livekit "github.com/livekit/livekit-server/proto" - "github.com/livekit/protocol/utils" ) -func TestTurnServer(t *testing.T) { +func testTurnServer(t *testing.T) { conf, err := config.NewConfig("") require.NoError(t, err) @@ -47,15 +48,15 @@ func TestTurnServer(t *testing.T) { require.NoError(t, roomStore.CreateRoom(rm)) turnConf := &turn.ClientConfig{ - STUNServerAddr: fmt.Sprintf("localhost:%d", conf.TURN.UDPPort), - TURNServerAddr: fmt.Sprintf("%s:%d", currentNode.Ip, conf.TURN.UDPPort), + STUNServerAddr: fmt.Sprintf("localhost:%d", conf.TURN.TLSPort), + TURNServerAddr: fmt.Sprintf("%s:%d", currentNode.Ip, conf.TURN.TLSPort), Username: rm.Name, Password: rm.TurnPassword, Realm: "livekit", } t.Run("TURN works over TCP", func(t *testing.T) { - conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", conf.TURN.TCPPort)) + conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", conf.TURN.TLSPort)) require.NoError(t, err) tc := *turnConf