mirror of
https://github.com/jech/galene.git
synced 2024-11-22 16:45: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
|
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:
|
||||||
"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
|
||||||
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:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
--- 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/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() {
|
||||||
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() {
|
||||||
|
|
||||||
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)
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -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 (
|
||||||
"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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cf.ICEServers = append(cf.ICEServers, turnserver.ICEServers()...)
|
||||||
|
|
||||||
if ICERelayOnly {
|
if ICERelayOnly {
|
||||||
cf.ICETransportPolicy = webrtc.ICETransportPolicyRelay
|
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