Commit 5a7937b1 authored by Juliusz Chroboczek's avatar Juliusz Chroboczek

Add built-in TURN server.

parent a15915e8
...@@ -15,85 +15,6 @@ This step is optional. ...@@ -15,85 +15,6 @@ This step is optional.
echo 'god:topsecret' > data/passwd echo 'god:topsecret' > data/passwd
## Set up an ICE server
ICE is the NAT and firewall traversal protocol used by WebRTC. ICE uses
a variety of techniques for establishing a flow in the presence of
a firewall; the two most effective techniques, STUN and TURN, require help
from an external server. Whether you need a helping server depends both
on your firewalling setup and on the networks of your users; for
production use, you should probably use your own TURN server.
### No ICE server
If Galène is not firewalled (high-numbered ports are accessible from the
Internet) and none of your users are on a restrictive network, then you
need no ICE servers. There is nothing to do, skip to *Set up a group*
below.
### STUN server
If Galène might be behind a firewall (high-numbered ports might or might
not be accessible from the Internet), but none of your clients are on
a restrictive network, then a STUN server is enough. It is usually safe
to use a third-party STUN server, although doing that might violate the
privacy of your users. Your `data/ice-servers.json` file should look like
this:
[
{
"urls": [
"stun:stun.example.org"
]
}
]
### TURN server
In practice, some of your users will be on restrictive networks: many
enterprise networks only allow outgoing TCP to ports 80 and 443;
university networks tend to additionally allow outgoing traffic to port
1194. For best performance, your TURN server should be located close to
Galène and close to your users, so you will want to run your own. If you
use *coturn*, your `/etc/turnserver.conf` could look like this:
listening-port=443
lt-cred-mech
user=galene:secret
realm=galene.example.org
syslog
Your `ice-servers.json` should look like this:
[
{
"urls": [
"turn:turn.example.org:443",
"turn:turn.example.org:443?transport=tcp"
],
"username": "galene",
"credential": "secret"
}
]
If you prefer to use coturn's `use-auth-secret` option, then your
`ice-servers.json` should look like this:
[
{
"urls": [
"turn:turn.example.com:443",
"turn:turn.example.com:443?transport=tcp"
],
"username": "galene",
"credential": "secret",
"credentialType": "hmac-sha1"
}
]
For redundancy, you may set up multiple TURN servers, and ICE will use the
first one that works.
## Set up a group ## Set up a group
A group is set up by creating a file `groups/name.json`. A group is set up by creating a file `groups/name.json`.
...@@ -115,10 +36,7 @@ A group with one operator and two users looks like this: ...@@ -115,10 +36,7 @@ A group with one operator and two users looks like this:
"op": [{"username": "jch", "password": "1234"}], "op": [{"username": "jch", "password": "1234"}],
"presenter": [ "presenter": [
{"username": "mom", "password": "0000"}, {"username": "mom", "password": "0000"},
{ {"username": "dad", "password": "1234"}
"username": "dad",
"password": "Pójdźże, kiń tę chmurność w głąb flaszy!"
}
] ]
} }
...@@ -138,6 +56,27 @@ that the relay test has been successful. (The relay test will fail if you ...@@ -138,6 +56,27 @@ that the relay test has been successful. (The relay test will fail if you
didn't configure a TURN server; this is normal, and nothing to worry didn't configure a TURN server; this is normal, and nothing to worry
about.) about.)
## Configure your server's firewall
If your server has a global IPv4 address and there is no firewall, there
is nothing to do.
If your server has a global IPv4 address, then the firewall must, at
a strict minimum, allow incoming traffic to TCP port 8443 (or whatever is
configured with the `-http` command-line option) and TCP port 1194 (or
whatever is configured with the `-turn` command-line option). For best
performance, it should also allow UDP traffic to the TURN port and UDP
traffic to ephemeral (high-numbered) ports.
If your server only has a global IPv6 address, then you should probably
disable the built-in TURN server (`-turn ""`) and configure an external
TURN server; see "ICE Servers" below.
If your server is behind NAT, then you should configure your NAT device to
forward, at a minimum, ports 8443 and 1194. In addition, you should add
the option `-turn 192.0.2.1:1194` to Galène's command line, where `192.0.2.1`
is your NAT's external (global) IPv4 address.
## Deploy to your server ## Deploy to your server
Set up a user *galene* on your server, then do: Set up a user *galene* on your server, then do:
...@@ -298,4 +237,70 @@ user entry with a hashed password looks like this: ...@@ -298,4 +237,70 @@ user entry with a hashed password looks like this:
} }
} }
--- Juliusz Chroboczek <https://www.irif.fr/~jch/> # ICE Servers
ICE is the NAT and firewall traversal protocol used by WebRTC. ICE can
make use of two kinds of servers to help with NAT traversal: STUN servers,
that simply help punching holes in NATs, and TURN servers, that serve as
relays for traffic. TURN is a superset of NAT: no STUN server is
necessary if a TURN server is available.
Galène includes a simple IPv4-only TURN server, which is controlled by the
`-turn` command-line option. If the value of this option is the empty
string `""`, then the built-in server is disabled. If the value of this
option is a colon followed with a port number `:1194`, then the TURN
server will listen on all public IPv4 addresses of the local host, over
UDP and TCP. If the value of this option is a socket address, such as
`192.0.2.1:1194`, then the TURN server will listen on all addresses of the
local host but assume that the address seen by the clients is the one
given in the option; this is the recommended configuration when running
behind NAT with port forwarding.
Some users may prefer to disable Galène's built in TURN server (`-turn ""`)
and configure an external ICE server. In that case, the ICE configuration
should appear in the file `data/ice-servers.json`. In the case of a STUN
server, it should look like this:
[
{
"urls": [
"stun:stun.example.org"
]
}
]
In the case of s single TURN server, the `ice-servers.json` file should
look like this:
[
{
"urls": [
"turn:turn.example.org:443",
"turn:turn.example.org:443?transport=tcp"
],
"username": "galene",
"credential": "secret"
}
]
If you prefer to use coturn's `use-auth-secret` option, then your
`ice-servers.json` should look like this:
[
{
"Urls": [
"turn:turn.example.com:443",
"turn:turn.example.com:443?transport=tcp"
],
"username": "galene",
"credential": "secret",
"credentialType": "hmac-sha1"
}
]
For redundancy, you may set up multiple TURN servers, and ICE will use the
first one that works. If an `ice-servers.json` file is present and
Galène's built-in TURN server is enabled, then the external server will be
used in preference to the built-in server.
-- Juliusz Chroboczek <https://www.irif.fr/~jch/>
...@@ -14,6 +14,7 @@ import ( ...@@ -14,6 +14,7 @@ import (
"github.com/jech/galene/diskwriter" "github.com/jech/galene/diskwriter"
"github.com/jech/galene/group" "github.com/jech/galene/group"
"github.com/jech/galene/ice" "github.com/jech/galene/ice"
"github.com/jech/galene/turnserver"
"github.com/jech/galene/webserver" "github.com/jech/galene/webserver"
) )
...@@ -42,6 +43,8 @@ func main() { ...@@ -42,6 +43,8 @@ func main() {
flag.BoolVar(&group.UseMDNS, "mdns", false, "gather mDNS addresses") flag.BoolVar(&group.UseMDNS, "mdns", false, "gather mDNS addresses")
flag.BoolVar(&ice.ICERelayOnly, "relay-only", false, flag.BoolVar(&ice.ICERelayOnly, "relay-only", false,
"require use of TURN relays for all media traffic") "require use of TURN relays for all media traffic")
flag.StringVar(&turnserver.Address, "turn", ":1194",
"built-in TURN server address (\"\" to disable)")
flag.Parse() flag.Parse()
if cpuprofile != "" { if cpuprofile != "" {
...@@ -86,6 +89,12 @@ func main() { ...@@ -86,6 +89,12 @@ func main() {
go group.ReadPublicGroups() go group.ReadPublicGroups()
err := turnserver.Start()
if err != nil {
log.Printf("TURN: %v", err)
}
defer turnserver.Stop()
serverDone := make(chan struct{}) serverDone := make(chan struct{})
go func() { go func() {
err := webserver.Serve(httpAddr, dataDir) err := webserver.Serve(httpAddr, dataDir)
......
...@@ -9,6 +9,7 @@ require ( ...@@ -9,6 +9,7 @@ require (
github.com/pion/rtcp v1.2.6 github.com/pion/rtcp v1.2.6
github.com/pion/rtp v1.6.2 github.com/pion/rtp v1.6.2
github.com/pion/sdp/v3 v3.0.4 github.com/pion/sdp/v3 v3.0.4
github.com/pion/turn/v2 v2.0.5
github.com/pion/webrtc/v3 v3.0.3 github.com/pion/webrtc/v3 v3.0.3
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
) )
...@@ -14,6 +14,8 @@ import ( ...@@ -14,6 +14,8 @@ import (
"time" "time"
"github.com/pion/webrtc/v3" "github.com/pion/webrtc/v3"
"github.com/jech/galene/turnserver"
) )
type Server struct { type Server struct {
...@@ -101,6 +103,8 @@ func updateICEConfiguration() *configuration { ...@@ -101,6 +103,8 @@ func updateICEConfiguration() *configuration {
} }
} }
cf.ICEServers = append(cf.ICEServers, turnserver.ICEServers()...)
if ICERelayOnly { if ICERelayOnly {
cf.ICETransportPolicy = webrtc.ICETransportPolicyRelay cf.ICETransportPolicy = webrtc.ICETransportPolicyRelay
} }
......
package turnserver
import (
"crypto/rand"
"encoding/base64"
"errors"
"log"
"net"
"strconv"
"github.com/pion/turn/v2"
"github.com/pion/webrtc/v3"
)
var username string
var password string
var server *turn.Server
var Address string
var addresses []net.Addr
func publicAddresses() ([]net.IP, error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil, err
}
var as []net.IP
for _, addr := range addrs {
switch addr := addr.(type) {
case *net.IPNet:
a := addr.IP.To4()
if a == nil {
continue
}
if !a.IsGlobalUnicast() {
continue
}
if a[0] == 10 ||
a[0] == 172 && a[1] >= 16 && a[1] < 32 ||
a[0] == 192 && a[1] == 168 {
continue
}
as = append(as, a)
}
}
return as, nil
}
func listener(a net.IP, port int, relay net.IP) (*turn.PacketConnConfig, *turn.ListenerConfig) {
var pcc *turn.PacketConnConfig
var lc *turn.ListenerConfig
s := net.JoinHostPort(a.String(), strconv.Itoa(port))
p, err := net.ListenPacket("udp4", s)
if err == nil {
pcc = &turn.PacketConnConfig{
PacketConn: p,
RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{
RelayAddress: relay,
Address: a.String(),
},
}
} else {
log.Printf("TURN: listenPacket(%v): %v", s, err)
}
l, err := net.Listen("tcp4", s)
if err == nil {
lc = &turn.ListenerConfig{
Listener: l,
RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{
RelayAddress: relay,
Address: a.String(),
},
}
} else {
log.Printf("TURN: listen(%v): %v", s, err)
}
return pcc, lc
}
func Start() error {
if server != nil {
return errors.New("TURN server already started")
}
if Address == "" {
return errors.New("built-in TURN server disabled")
}
addr, err := net.ResolveUDPAddr("udp4", Address)
if err != nil {
return err
}
username = "galene"
buf := make([]byte, 6)
_, err = rand.Read(buf)
if err != nil {
return err
}
buf2 := make([]byte, 8)
base64.RawStdEncoding.Encode(buf2, buf)
password = string(buf2)
var lcs []turn.ListenerConfig
var pccs []turn.PacketConnConfig
if addr.IP != nil && !addr.IP.IsUnspecified() {
a := addr.IP.To4()
if a == nil {
return errors.New("couldn't parse address")
}
pcc, lc := listener(net.IP{0, 0, 0, 0}, addr.Port, a)
if pcc != nil {
pccs = append(pccs, *pcc)
addresses = append(addresses, &net.UDPAddr{
IP: a,
Port: addr.Port,
})
}
if lc != nil {
lcs = append(lcs, *lc)
addresses = append(addresses, &net.TCPAddr{
IP: a,
Port: addr.Port,
})
}
} else {
as, err := publicAddresses()
if err != nil {
return err
}
if len(as) == 0 {
return errors.New("no public addresses")
}
for _, a := range as {
pcc, lc := listener(a, addr.Port, a)
if pcc != nil {
pccs = append(pccs, *pcc)
addresses = append(addresses, &net.UDPAddr{
IP: a,
Port: addr.Port,
})
}
if lc != nil {
lcs = append(lcs, *lc)
addresses = append(addresses, &net.TCPAddr{
IP: a,
Port: addr.Port,
})
}
}
}
if len(pccs) == 0 && len(lcs) == 0 {
return errors.New("couldn't establish any listeners")
}
server, err = turn.NewServer(turn.ServerConfig{
Realm: "galene.org",
AuthHandler: func(u, r string, src net.Addr) ([]byte, bool) {
if u != username || r != "galene.org" {
return nil, false
}
return turn.GenerateAuthKey(u, r, password), true
},
ListenerConfigs: lcs,
PacketConnConfigs: pccs,
})
if err != nil {
addresses = nil
return err
}
return nil
}
func ICEServers() []webrtc.ICEServer {
if len(addresses) == 0 {
return nil
}
var urls []string
for _, a := range addresses {
switch a := a.(type) {
case *net.UDPAddr:
urls = append(urls, "turn:"+a.String())
case *net.TCPAddr:
urls = append(urls, "turn:"+a.String()+"?transport=tcp")
default:
log.Printf("unexpected TURN address %T", a)
}
}
return []webrtc.ICEServer{
{
URLs: urls,
Username: username,
Credential: password,
},
}
}
func Stop() {
addresses = nil
if server == nil {
return
}
server.Close()
server = nil
return
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment