mirror of
https://github.com/jech/galene.git
synced 2024-11-09 18:25:58 +01:00
Add built-in TURN server.
This commit is contained in:
parent
a15915e8fc
commit
5a7937b198
5 changed files with 322 additions and 84 deletions
173
README
173
README
|
@ -15,85 +15,6 @@ This step is optional.
|
|||
|
||||
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
|
||||
|
||||
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:
|
|||
"op": [{"username": "jch", "password": "1234"}],
|
||||
"presenter": [
|
||||
{"username": "mom", "password": "0000"},
|
||||
{
|
||||
"username": "dad",
|
||||
"password": "Pójdźże, kiń tę chmurność w głąb flaszy!"
|
||||
}
|
||||
{"username": "dad", "password": "1234"}
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
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
|
||||
|
||||
Set up a user *galene* on your server, then do:
|
||||
|
@ -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 (
|
|||
"github.com/jech/galene/diskwriter"
|
||||
"github.com/jech/galene/group"
|
||||
"github.com/jech/galene/ice"
|
||||
"github.com/jech/galene/turnserver"
|
||||
"github.com/jech/galene/webserver"
|
||||
)
|
||||
|
||||
|
@ -42,6 +43,8 @@ func main() {
|
|||
flag.BoolVar(&group.UseMDNS, "mdns", false, "gather mDNS addresses")
|
||||
flag.BoolVar(&ice.ICERelayOnly, "relay-only", false,
|
||||
"require use of TURN relays for all media traffic")
|
||||
flag.StringVar(&turnserver.Address, "turn", ":1194",
|
||||
"built-in TURN server address (\"\" to disable)")
|
||||
flag.Parse()
|
||||
|
||||
if cpuprofile != "" {
|
||||
|
@ -86,6 +89,12 @@ func main() {
|
|||
|
||||
go group.ReadPublicGroups()
|
||||
|
||||
err := turnserver.Start()
|
||||
if err != nil {
|
||||
log.Printf("TURN: %v", err)
|
||||
}
|
||||
defer turnserver.Stop()
|
||||
|
||||
serverDone := make(chan struct{})
|
||||
go func() {
|
||||
err := webserver.Serve(httpAddr, dataDir)
|
||||
|
|
1
go.mod
1
go.mod
|
@ -9,6 +9,7 @@ require (
|
|||
github.com/pion/rtcp v1.2.6
|
||||
github.com/pion/rtp v1.6.2
|
||||
github.com/pion/sdp/v3 v3.0.4
|
||||
github.com/pion/turn/v2 v2.0.5
|
||||
github.com/pion/webrtc/v3 v3.0.3
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||
)
|
||||
|
|
|
@ -14,6 +14,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pion/webrtc/v3"
|
||||
|
||||
"github.com/jech/galene/turnserver"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
|
@ -101,6 +103,8 @@ func updateICEConfiguration() *configuration {
|
|||
}
|
||||
}
|
||||
|
||||
cf.ICEServers = append(cf.ICEServers, turnserver.ICEServers()...)
|
||||
|
||||
if ICERelayOnly {
|
||||
cf.ICETransportPolicy = webrtc.ICETransportPolicyRelay
|
||||
}
|
||||
|
|
219
turnserver/turnserver.go
Normal file
219
turnserver/turnserver.go
Normal file
|
@ -0,0 +1,219 @@
|
|||
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
|
||||
}
|
Loading…
Reference in a new issue