Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added an example with TURN #102

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 65 additions & 0 deletions sfu-ws-turn/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# sfu-ws-turn
sfu-ws-turn is a many-to-many websocket based SFU. This is a more advanced version of [sfu-ws](https://github.com/pion/webrtc/tree/master/examples/sfu-ws)
and demonstrates the following features.

* Trickle ICE
* Re-negotiation
* Basic RTCP
* Multiple inbound/outbound tracks per PeerConnection
* No codec restriction per call. You can have H264 and VP8 in the same conference.
* Support for multiple browsers
* TURN
* Non-local testing

For a production application you should also explore [simulcast](https://github.com/pion/webrtc/tree/master/examples/simulcast),
metrics and robust error handling and check all TODOs for hardening

## Instructions
### Download sfu-ws-turn
This example requires you to clone the repo since it is serving static HTML.

```
mkdir -p $GOPATH/src/github.com/pion
cd $GOPATH/src/github.com/pion
git clone https://github.com/pion/example-webrtc-applications.git
cd webrtc/examples/sfu-ws-turn
```

### Create certificates
```./prep-certs```
This creates a self signed CA and a server certificate for the turn server, and the web servers.

### Fix certificate path in coturn-user-management
Tweak ./coturn-use-management/turnserver.conf as required

### Install coturn
```sudo apt install -y coturn```
This is the TURN server, for use with firewalls

### Start the turn server
```sudo turnserver -c ./coturn-user-management/turnserver.conf --daemon```

### Add relevant lines to hosts files
This will need done on all machines you want to test from
```sudo vim /etc/hosts```
and add the following, changing 127.0.0.1 to the IP of your server
```127.0.0.1 example.com
127.0.0.1 turn.example.com
```


### Run the credential server
```go build -o ./coturn-user-management/ ./coturn-user-management/main.go && sudo ./coturn-user-management/main -cert /tmp/sfu-ws-turn-certs/server.crt -cert-key /tmp/sfu-ws-turn-certs/server.key
```
sudo is required so it has write access to the coturn db.
This enables dynamic credentials for the TURN server.

### Run sfu-ws-turn
Execute `go run *.go` with TODO flags
```go build main.go && sudo ./main -cert /tmp/sfu-ws-turn-certs/server.crt -cert-key /tmp/sfu-ws-turn-certs/server.key -cred-URL https://example.com:8443/20987182471824882098 -insecure-reqs true
```

### Open the Web UI
Open [https://example.com](https://example.com). This will automatically connect and send your video. Now join from other tabs and browsers!

Congratulations, you have used Pion WebRTC! Now start building something cool
21 changes: 21 additions & 0 deletions sfu-ws-turn/ca.cnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
x509_extensions = v3_req

[dn]
C=GB
OU=Engineering
emailAddress=admin@localhost
CN = localCA.local



[ v3_req ]
# Extensions for a typical CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
12 changes: 12 additions & 0 deletions sfu-ws-turn/coturn-user-management/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module coturn-user-management

go 1.16

require (
github.com/banzaicloud/logrus-runtime-formatter v0.0.0-20190729070250-5ae5475bae5e // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/mattn/go-sqlite3 v1.14.6 // indirect
github.com/pion/rtcp v1.2.6 // indirect
github.com/pion/webrtc/v3 v3.0.12 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
)
139 changes: 139 additions & 0 deletions sfu-ws-turn/coturn-user-management/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package main

import (
"crypto/md5"
"database/sql"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"math/rand"
"net/http"
"time"

runtime "github.com/banzaicloud/logrus-runtime-formatter"
_ "github.com/mattn/go-sqlite3"
log "github.com/sirupsen/logrus"
)

var (
dsn = flag.String("dsn", "/var/lib/turn/turndb", "DSN for the sqlite3 db")
addr = flag.String("addr", ":8443", "address to listen to")
certKeyPath = flag.String("cert-key", "cert-key", "path to the tls certificate private key")
certPath = flag.String("cert", "cert", "path to the tls certificate")
realm = flag.String("realm", "turn.example.com", "TURN realm")
url = flag.String("url", "https://example.com", "URL to allow for CORS")
)

type tempCredentials struct {
UserName string `json:"Username"`
Password string `json:"Password`
}

func generateTempCredentialsHash(user string, realm string, password string) (hashStr string) {
hash := md5.Sum([]byte(fmt.Sprintf("%s:%s:%s", user, realm, password)))

return hex.EncodeToString(hash[:])
}

func generateTempCredentials(db *sql.DB) (tc tempCredentials, err error) {

un := fmt.Sprintf("%d", time.Now().UnixNano())
pw := fmt.Sprintf("%d", rand.Uint64()) // TODO: use crypto/rand instead for prod ready crypto

tc = tempCredentials{UserName: un, Password: pw}
hsh := generateTempCredentialsHash(un, *realm, pw)

err = writeTempCredentials(db, *realm, un, string(hsh))
if err != nil {
log.Info(err)
tc = tempCredentials{}
return
}

return tc, err
}

func writeTempCredentials(db *sql.DB, realm string, username string, hash string) (err error) {

// write them
// return them
tx, err := db.Begin()
if err != nil {
return err
}

defer tx.Rollback()
stmt, err := tx.Prepare("INSERT INTO turnusers_lt (realm, name, hmackey) VALUES (?,?,?)")
if err != nil {
return err
}
_, err = stmt.Exec(realm, username, hash)
if err != nil {
return err
}
err = tx.Commit()
return err
}

func returnTempCredentials(w http.ResponseWriter, r *http.Request, db *sql.DB) {
log.Infof("Creating credentials for %v", r.RemoteAddr)
creds, err := generateTempCredentials(db)
w.Header().Set("Access-Control-Allow-Origin", *url)
if err != nil {
log.Info(err)
w.WriteHeader(500) // Return 500 Internal Server Error.
return
}
json.NewEncoder(w).Encode(creds)
}

func cleanupOldUsers(db *sql.DB) {
_, err := db.Exec("DELETE FROM turnusers_lt WHERE `name` < strftime('%s', DATE('now', '-1 days'))") // OK
if err != nil {
log.Info(err)
}
}

// loopCleanupOldUsers wipes old users every hour that lasted more than 24hrs
func loopCleanupOldUsers(db *sql.DB) {
for {
log.Info("Cleaning up old users")
cleanupOldUsers(db)
time.Sleep(60 * 60 * time.Second) // Once an hour
}
}

func init() {
log.SetLevel(log.DebugLevel)
formatter := runtime.Formatter{ChildFormatter: &log.TextFormatter{
FullTimestamp: true,
}}
formatter.Line = true
log.SetFormatter(&formatter)

}

func main() {
// Parse the flags passed to program
flag.Parse()

log.Print("Connecting to DB")
database, err := sql.Open("sqlite3", *dsn)

if err != nil {
log.Panic(err)
}

// credential generation endpoint
// Wrapping the script to pass the right DB
// TODO: Something better than this if you're actually using it
http.HandleFunc("/20987182471824882098", func(w http.ResponseWriter, r *http.Request) { returnTempCredentials(w, r, database) })

// Cleanup old users in background
go loopCleanupOldUsers(database)

log.Print("Starting server")
// start HTTP server
log.Fatal(http.ListenAndServeTLS(*addr, *certPath, *certKeyPath, nil))
}
66 changes: 66 additions & 0 deletions sfu-ws-turn/coturn-user-management/turnserver.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# /etc/turnserver.conf

# STUN server port is 3478 for UDP and TCP, and 5349 for TLS.
# Allow connection on the UDP port 3478
listening-port=3478
# and 5349 for TLS (secure)
tls-listening-port=5349

# Require authentication
fingerprint
lt-cred-mech

# We will use the longterm authentication mechanism, but if
# you want to use the auth-secret mechanism, comment lt-cred-mech and
# uncomment use-auth-secret
# Check: https://github.com/coturn/coturn/issues/180#issuecomment-364363272
#The static auth secret needs to be changed, in this tutorial
# we'll generate a token using OpenSSL
# use-auth-secret
# static-auth-secret=replace-this-secret
# ----
# If you decide to use use-auth-secret, After saving the changes, change the auth-secret using the following command:
# sed -i "s/replace-this-secret/$(openssl rand -hex 32)/" /etc/turnserver.conf
# This will replace the replace-this-secret text on the file with the generated token using openssl.

# Specify the server name and the realm that will be used
# if is your first time configuring, just use the domain as name
server-name=turn.example.com
realm=turn.example.com


# Important:
# Create a test user if you want
# You can remove this user after testing
#user=guest:somepassword
#user=SAMPLEUSER:zvpinQLeOmOrG8ipnXyvJkY8Wtmm4K5UYeaL0rAOTlNgduIDKzdGoQkYBt3D3wRB

total-quota=100
stale-nonce=600

# Path to the SSL certificate and private key. In this example we will use
# the letsencrypt generated certificate files.
# cert=/etc/letsencrypt/live/example.com/cert.pem
# pkey=/etc/letsencrypt/live/example.com/privkey.pem

# TODO: Use real certs
cert=/tmp/sfu-ws-turn-certs/server.crt
pkey=/tmp/sfu-ws-turn-certs/server.key

dh2066

# Specify the allowed OpenSSL cipher list for TLS/DTLS connections
# cipher-list="ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384"

# Specify the process user and group
proc-user=turnserver


verbose
no-software-attribute
no-multicast-peers
#secure-stun


no-udp
no-tcp