1
Fork 0
galene/ice/ice.go

232 lines
4.8 KiB
Go
Raw Normal View History

package ice
import (
2021-01-01 23:50:34 +01:00
"bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"encoding/json"
"errors"
2021-01-01 23:50:34 +01:00
"fmt"
"log"
"os"
2020-12-28 04:06:12 +01:00
"sync/atomic"
"time"
"github.com/pion/webrtc/v3"
2021-01-18 20:24:52 +01:00
"github.com/jech/galene/turnserver"
)
type Server struct {
URLs []string `json:"urls"`
Username string `json:"username,omitempty"`
Credential interface{} `json:"credential,omitempty"`
CredentialType string `json:"credentialType,omitempty"`
}
func getServer(server Server) (webrtc.ICEServer, error) {
s := webrtc.ICEServer{
URLs: server.URLs,
Username: server.Username,
Credential: server.Credential,
}
switch server.CredentialType {
case "", "password":
s.CredentialType = webrtc.ICECredentialTypePassword
case "oauth":
s.CredentialType = webrtc.ICECredentialTypeOauth
2021-01-01 23:50:34 +01:00
case "hmac-sha1":
cred, ok := server.Credential.(string)
if !ok {
return webrtc.ICEServer{},
errors.New("credential is not a string")
}
ts := time.Now().Unix() + 86400
var username string
if server.Username == "" {
username = fmt.Sprintf("%d", ts)
} else {
username = fmt.Sprintf("%d:%s", ts, server.Username)
}
mac := hmac.New(sha1.New, []byte(cred))
mac.Write([]byte(username))
buf := bytes.Buffer{}
e := base64.NewEncoder(base64.StdEncoding, &buf)
e.Write(mac.Sum(nil))
e.Close()
s.Username = username
s.Credential = string(buf.Bytes())
s.CredentialType = webrtc.ICECredentialTypePassword
default:
return webrtc.ICEServer{}, errors.New("unsupported credential type")
}
return s, nil
}
var ICEFilename string
var ICERelayOnly bool
type configuration struct {
conf webrtc.Configuration
2020-12-28 04:06:12 +01:00
timestamp time.Time
}
var conf atomic.Value
2020-12-28 04:06:12 +01:00
func updateICEConfiguration() *configuration {
2020-12-28 04:06:12 +01:00
now := time.Now()
var cf webrtc.Configuration
2020-12-28 04:06:12 +01:00
if ICEFilename != "" {
file, err := os.Open(ICEFilename)
if err != nil {
if !os.IsNotExist(err) {
log.Printf("Open %v: %v", ICEFilename, err)
}
2020-12-28 04:06:12 +01:00
} else {
defer file.Close()
d := json.NewDecoder(file)
var servers []Server
err = d.Decode(&servers)
2020-12-28 04:06:12 +01:00
if err != nil {
log.Printf("Get ICE configuration: %v", err)
}
for _, s := range servers {
ss, err := getServer(s)
if err != nil {
log.Printf("parse ICE server: %v", err)
continue
}
cf.ICEServers = append(cf.ICEServers, ss)
}
}
2020-12-28 04:06:12 +01:00
}
2021-01-18 20:24:52 +01:00
cf.ICEServers = append(cf.ICEServers, turnserver.ICEServers()...)
2020-12-28 04:06:12 +01:00
if ICERelayOnly {
cf.ICETransportPolicy = webrtc.ICETransportPolicyRelay
2020-12-28 04:06:12 +01:00
}
iceConf := configuration{
conf: cf,
2020-12-28 04:06:12 +01:00
timestamp: now,
}
conf.Store(&iceConf)
return &iceConf
}
func ICEConfiguration() *webrtc.Configuration {
conf, ok := conf.Load().(*configuration)
2020-12-28 04:06:12 +01:00
if !ok || time.Since(conf.timestamp) > 5*time.Minute {
conf = updateICEConfiguration()
} else if time.Since(conf.timestamp) > 2*time.Minute {
go updateICEConfiguration()
}
return &conf.conf
}
2021-01-14 01:47:56 +01:00
func RelayTest() (time.Duration, error) {
2021-01-14 02:07:00 +01:00
2021-01-14 01:47:56 +01:00
conf := ICEConfiguration()
conf2 := *conf
conf2.ICETransportPolicy = webrtc.ICETransportPolicyRelay
2021-01-14 02:07:00 +01:00
var s webrtc.SettingEngine
s.SetHostAcceptanceMinWait(0)
s.SetSrflxAcceptanceMinWait(0)
s.SetPrflxAcceptanceMinWait(0)
s.SetRelayAcceptanceMinWait(0)
api := webrtc.NewAPI(webrtc.WithSettingEngine(s))
pc1, err := api.NewPeerConnection(conf2)
2021-01-14 01:47:56 +01:00
if err != nil {
return 0, err
}
defer pc1.Close()
pc2, err := webrtc.NewPeerConnection(*conf)
if err != nil {
return 0, err
}
defer pc2.Close()
pc1.OnICECandidate(func(c *webrtc.ICECandidate) {
if c != nil {
pc2.AddICECandidate(c.ToJSON())
}
})
pc2.OnICECandidate(func(c *webrtc.ICECandidate) {
if c != nil {
pc1.AddICECandidate(c.ToJSON())
}
})
d1, err := pc1.CreateDataChannel("loopback", nil)
if err != nil {
return 0, err
}
ch1 := make(chan error, 1)
d1.OnOpen(func() {
err := d1.Send([]byte(time.Now().Format(time.RFC3339Nano)))
if err != nil {
select {
case ch1 <- err:
default:
}
}
})
offer, err := pc1.CreateOffer(nil)
if err != nil {
return 0, err
}
err = pc1.SetLocalDescription(offer)
if err != nil {
return 0, err
}
err = pc2.SetRemoteDescription(*pc1.LocalDescription())
if err != nil {
return 0, err
}
answer, err := pc2.CreateAnswer(nil)
if err != nil {
return 0, err
}
err = pc2.SetLocalDescription(answer)
if err != nil {
return 0, err
}
err = pc1.SetRemoteDescription(*pc2.LocalDescription())
if err != nil {
return 0, err
}
ch2 := make(chan string, 1)
pc2.OnDataChannel(func(d2 *webrtc.DataChannel) {
d2.OnMessage(func(msg webrtc.DataChannelMessage) {
select {
case ch2 <- string(msg.Data):
default:
}
})
})
timer := time.NewTimer(20 * time.Second)
defer timer.Stop()
select {
case err := <-ch1:
return 0, err
case msg := <-ch2:
tm, err := time.Parse(time.RFC3339Nano, msg)
if err != nil {
return 0, err
}
return time.Now().Sub(tm), nil
case <-timer.C:
return 0, errors.New("timeout")
}
}