From 2388723fa5334f7908fc287990dddcf94d8cb72f Mon Sep 17 00:00:00 2001 From: bel Date: Sun, 3 May 2020 23:09:46 -0600 Subject: [PATCH] serve tls traffic and static files to view webcam locally and open websocket conn --- .gitignore | 2 + config.go | 17 ++++++++ main.go | 34 +++++++++++++++ public/index.html | 27 ++++++++++++ public/webrtc.js | 103 ++++++++++++++++++++++++++++++++++++++++++++++ server.go | 31 ++++++++++++++ ws.go | 53 ++++++++++++++++++++++++ 7 files changed, 267 insertions(+) create mode 100644 config.go create mode 100644 main.go create mode 100755 public/index.html create mode 100755 public/webrtc.js create mode 100644 server.go create mode 100644 ws.go diff --git a/.gitignore b/.gitignore index 7d08791..adc6a75 100755 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ entropy +vendor exec-entropy **.sw* **/**.sw* *.sw* **/*.sw* +testdata diff --git a/config.go b/config.go new file mode 100644 index 0000000..93fdaa2 --- /dev/null +++ b/config.go @@ -0,0 +1,17 @@ +package main + +import "local/args" + +func config() *args.ArgSet { + as := args.NewArgSet() + + as.Append(args.INT, "p", "port to listen on", "58080") + as.Append(args.STRING, "d", "root dir to serve static", ".") + as.Append(args.STRING, "crt", "path to crt", "./cert.crt") + as.Append(args.STRING, "key", "path to key", "./cert.key") + + if err := as.Parse(); err != nil { + panic(err) + } + return as +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..b52577d --- /dev/null +++ b/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "crypto/tls" + "fmt" + "log" + "net/http" +) + +func main() { + as := config() + httpsServer := &http.Server{ + Addr: fmt.Sprintf(":%d", as.GetInt("p")), + Handler: New(), + TLSConfig: &tls.Config{ + MinVersion: tls.VersionTLS12, + CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, + PreferServerCipherSuites: true, + CipherSuites: []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_RSA_WITH_AES_256_CBC_SHA, + }, + }, + TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0), + } + c := as.GetString("crt") + k := as.GetString("key") + log.Printf("listening on %q", httpsServer.Addr) + if err := httpsServer.ListenAndServeTLS(c, k); err != nil { + panic(err) + } +} diff --git a/public/index.html b/public/index.html new file mode 100755 index 0000000..0f1299f --- /dev/null +++ b/public/index.html @@ -0,0 +1,27 @@ + + + + + + + + + + + + +
+ + + + + + diff --git a/public/webrtc.js b/public/webrtc.js new file mode 100755 index 0000000..97c860d --- /dev/null +++ b/public/webrtc.js @@ -0,0 +1,103 @@ +var localVideo; +var localStream; +var remoteVideo; +var peerConnection; +var uuid; +var serverConnection; + +var peerConnectionConfig = { + 'iceServers': [ + {'urls': 'stun:stun.stunprotocol.org:3478'}, + {'urls': 'stun:stun.l.google.com:19302'}, + ] +}; + +function pageReady() { + uuid = createUUID(); + + localVideo = document.getElementById('localVideo'); + remoteVideo = document.getElementById('remoteVideo'); + + serverConnection = new WebSocket('wss://' + window.location.hostname + '/abc'); + serverConnection.onmessage = gotMessageFromServer; + + var constraints = { + video: true, + audio: true, + }; + + if(navigator.mediaDevices.getUserMedia) { + navigator.mediaDevices.getUserMedia(constraints).then(getUserMediaSuccess).catch(errorHandler); + } else { + alert('Your browser does not support getUserMedia API'); + } +} + +function getUserMediaSuccess(stream) { + localStream = stream; + localVideo.srcObject = stream; +} + +function start(isCaller) { + peerConnection = new RTCPeerConnection(peerConnectionConfig); + peerConnection.onicecandidate = gotIceCandidate; + peerConnection.ontrack = gotRemoteStream; + peerConnection.addStream(localStream); + + if(isCaller) { + peerConnection.createOffer().then(createdDescription).catch(errorHandler); + } +} + +function gotMessageFromServer(message) { + if(!peerConnection) start(false); + + var signal = JSON.parse(message.data); + + // Ignore messages from ourself + if(signal.uuid == uuid) return; + + if(signal.sdp) { + peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp)).then(function() { + // Only create answers in response to offers + if(signal.sdp.type == 'offer') { + peerConnection.createAnswer().then(createdDescription).catch(errorHandler); + } + }).catch(errorHandler); + } else if(signal.ice) { + peerConnection.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(errorHandler); + } +} + +function gotIceCandidate(event) { + if(event.candidate != null) { + serverConnection.send(JSON.stringify({'ice': event.candidate, 'uuid': uuid})); + } +} + +function createdDescription(description) { + console.log('got description'); + + peerConnection.setLocalDescription(description).then(function() { + serverConnection.send(JSON.stringify({'sdp': peerConnection.localDescription, 'uuid': uuid})); + }).catch(errorHandler); +} + +function gotRemoteStream(event) { + console.log('got remote stream'); + remoteVideo.srcObject = event.streams[0]; +} + +function errorHandler(error) { + console.log(error); +} + +// Taken from http://stackoverflow.com/a/105074/515584 +// Strictly speaking, it's not a real UUID, but it gets the job done here +function createUUID() { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); + } + + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..86eab94 --- /dev/null +++ b/server.go @@ -0,0 +1,31 @@ +package main + +import ( + "log" + "net/http" + "os" + "path" +) + +type Server struct { + fs http.Handler + ws *WS +} + +func New() *Server { + fs := http.FileServer(http.Dir(config().GetString("d"))) + return &Server{ + fs: fs, + ws: NewWS(), + } +} + +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + log.Println("serving", r.URL) + if _, err := os.Stat(path.Join(config().GetString("d"), r.URL.Path[1:])); os.IsNotExist(err) { + s.ws.ServeHTTP(w, r) + } else { + log.Printf("Serving static %q from %q", r.URL.Path, config().GetString("d")) + s.fs.ServeHTTP(w, r) + } +} diff --git a/ws.go b/ws.go new file mode 100644 index 0000000..8d01927 --- /dev/null +++ b/ws.go @@ -0,0 +1,53 @@ +package main + +import ( + "io" + "log" + "net/http" + + "github.com/gorilla/websocket" +) + +type WS struct { + upgrader websocket.Upgrader +} + +func NewWS() *WS { + return &WS{ + upgrader: websocket.Upgrader{ + ReadBufferSize: 10240, + WriteBufferSize: 10240, + CheckOrigin: func(_ *http.Request) bool { return true }, + }, + } +} + +func (ws *WS) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if err := ws.serveHTTP(w, r); err != nil { + log.Println(r.URL.Path, err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} + +func (ws *WS) serveHTTP(w http.ResponseWriter, r *http.Request) error { + conn, err := ws.upgrader.Upgrade(w, r, nil) + if err != nil { + return err + } + for { + mt, r, err := conn.NextReader() + if err != nil { + return err + } + w, err := conn.NextWriter(mt) + if err != nil { + return err + } + if _, err := io.Copy(w, r); err != nil { // todo impl broadcast to channel;; sync map to all channels, goes to a forking reader-writer pipe, all listeners to broadcast read from pipe + return err + } + if err := w.Close(); err != nil { + return err + } + } +}