From cb772b46e326d4f37cb6fbb936b22da44218f93d Mon Sep 17 00:00:00 2001 From: Fritz Heiden Date: Mon, 24 Mar 2025 20:25:05 +0100 Subject: [PATCH] working: turn server websocket --- go.mod | 7 +++ go.sum | 14 +++++ main/main.go | 15 +++++ server/turn_server.go | 92 ++++++++++++++++++++++++++++++ server/websocket.go | 38 ++++++++++++ www/src/views/integration-view.jsx | 27 ++++++++- 6 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 server/turn_server.go create mode 100644 server/websocket.go diff --git a/go.mod b/go.mod index b32ad86..7330956 100644 --- a/go.mod +++ b/go.mod @@ -10,9 +10,16 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.24 // indirect + github.com/pion/dtls/v3 v3.0.1 // indirect + github.com/pion/logging v0.2.2 // indirect + github.com/pion/randutil v0.1.0 // indirect + github.com/pion/stun/v3 v3.0.0 // indirect + github.com/pion/transport/v3 v3.0.7 // indirect + github.com/pion/turn/v4 v4.0.0 // indirect github.com/rs/zerolog v1.33.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/wlynxg/anet v0.0.3 // indirect github.com/ziflex/lecho/v3 v3.7.0 // indirect golang.org/x/crypto v0.36.0 // indirect golang.org/x/net v0.33.0 // indirect diff --git a/go.sum b/go.sum index a8fa44b..c6cd307 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,18 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/pion/dtls/v3 v3.0.1 h1:0kmoaPYLAo0md/VemjcrAXQiSf8U+tuU3nDYVNpEKaw= +github.com/pion/dtls/v3 v3.0.1/go.mod h1:dfIXcFkKoujDQ+jtd8M6RgqKK3DuaUilm3YatAbGp5k= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= +github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= +github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= +github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= +github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM= +github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= @@ -25,6 +37,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/wlynxg/anet v0.0.3 h1:PvR53psxFXstc12jelG6f1Lv4MWqE0tI76/hHGjh9rg= +github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/ziflex/lecho/v3 v3.7.0 h1:MSzYINEHtAaCx2XpbdF1A85aSyXitNJxF4T9dG6jzRQ= github.com/ziflex/lecho/v3 v3.7.0/go.mod h1:LBlLsyIwa0MFxtJ2WU5WzHfuMR/jnq26TXddWfJ+s/0= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= diff --git a/main/main.go b/main/main.go index ad3bbf9..33a8501 100644 --- a/main/main.go +++ b/main/main.go @@ -70,6 +70,12 @@ func main() { deviceApiHandler.SetRouter(webServer.Router()) deviceApiHandler.Initialize(&authenticator) + turnServer := server.TurnServer{} + turnServer.SetPublicIp("192.168.178.23") + turnServer.SetPort(3478) + turnServer.SetRealm("pbrts.net") + turnServer.AddUser("user", "password") + var wg sync.WaitGroup wg.Add(1) go func() { @@ -80,6 +86,15 @@ func main() { log.Error().Err(err).Msg("failed to start web server") } }() + + go func() { + defer wg.Done() + log.Info().Msg("starting turn server") + err := turnServer.Start() + if err != nil { + log.Error().Err(err).Msg("failed to start turn server") + } + }() wg.Wait() } diff --git a/server/turn_server.go b/server/turn_server.go new file mode 100644 index 0000000..f2c19ec --- /dev/null +++ b/server/turn_server.go @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + +// Package main implements a simple TURN server +package server + +import ( + "fmt" + "net" + "os" + "os/signal" + "strconv" + "syscall" + + "github.com/pion/turn/v4" +) + +type TurnServer struct { + publicIp string + port int + users map[string][]byte + realm string +} + +func (t *TurnServer) Start() error { + // Create a UDP listener to pass into pion/turn + // pion/turn itself doesn't allocate any UDP sockets, but lets the user pass them in + // this allows us to add logging, storage or modify inbound/outbound traffic + udpListener, err := net.ListenPacket("udp4", "0.0.0.0:"+strconv.Itoa(t.port)) + if err != nil { + return fmt.Errorf("failed to create TURN server listener: %s", err) + } + + server, err := turn.NewServer(turn.ServerConfig{ + Realm: t.realm, + // Set AuthHandler callback + // This is called every time a user tries to authenticate with the TURN server + // Return the key for that user, or false when no user is found + AuthHandler: func(username string, realm string, srcAddr net.Addr) ([]byte, bool) { // nolint: revive + if key, ok := t.users[username]; ok { + return key, true + } + + return nil, false + }, + // PacketConnConfigs is a list of UDP Listeners and the configuration around them + PacketConnConfigs: []turn.PacketConnConfig{ + { + PacketConn: udpListener, + RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{ + // Claim that we are listening on IP passed by user (This should be your Public IP) + RelayAddress: net.ParseIP(t.publicIp), + // But actually be listening on every interface + Address: "0.0.0.0", + }, + }, + }, + }) + if err != nil { + return err + } + + // Block until user sends SIGINT or SIGTERM + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + <-sigs + + if err = server.Close(); err != nil { + return err + } + + return nil +} + +func (t *TurnServer) AddUser(username string, password string) { + if t.users == nil { + t.users = make(map[string][]byte) + } + t.users[username] = turn.GenerateAuthKey(username, t.realm, password) +} + +func (t *TurnServer) SetPublicIp(ip string) { + t.publicIp = ip +} + +func (t *TurnServer) SetPort(port int) { + t.port = port +} + +func (t *TurnServer) SetRealm(realm string) { + t.realm = realm +} diff --git a/server/websocket.go b/server/websocket.go new file mode 100644 index 0000000..8fdb01a --- /dev/null +++ b/server/websocket.go @@ -0,0 +1,38 @@ +package server + +import ( + "playback-device-server/data" + + "github.com/labstack/echo/v4" + "golang.org/x/net/websocket" +) + +type WebSocketConnection struct { + connection *websocket.Conn + integration data.Integration +} + +type WebSocket struct { + router *echo.Echo + connections []WebSocketConnection +} + +func (s *WebSocket) Initializer() { + s.router.GET("/ws", s.handle) +} + +func (s *WebSocket) handle(context echo.Context) error { + websocket.Handler(func(ws *websocket.Conn) { + defer ws.Close() + s.connections = append(s.connections, WebSocketConnection{ + connection: ws, + integration: data.Integration{}, + }) + }).ServeHTTP(context.Response(), context.Request()) + + return nil +} + +func (s *WebSocket) SetRouter(router *echo.Echo) { + s.router = router +} diff --git a/www/src/views/integration-view.jsx b/www/src/views/integration-view.jsx index 908cace..0b7675b 100644 --- a/www/src/views/integration-view.jsx +++ b/www/src/views/integration-view.jsx @@ -3,12 +3,34 @@ import Integration from "../data/integration"; const [integration, setIntegration] = createSignal(null); +const offer = { + type: "offer", + sdp: "v=0\r\no=- 3395492761835116674 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS 077a17e7-aec0-4aec-bef9-da6e02d3458f\r\nm=video 57551 UDP/TLS/RTP/SAVPF 96 97 103 104 107 108 109 114 115 116 117 118 39 40 45 46 98 99 100 101 119 120 121\r\nc=IN IP4 192.168.178.23\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=candidate:4029581777 1 udp 2122260223 192.168.178.23 57551 typ host generation 0 network-id 1 network-cost 10\r\na=candidate:2397137737 1 tcp 1518280447 192.168.178.23 9 typ host tcptype active generation 0 network-id 1 network-cost 10\r\na=ice-ufrag:BJUp\r\na=ice-pwd:7yhav/1dA688oD7sU/tb3TMI\r\na=ice-options:trickle\r\na=fingerprint:sha-256 DD:1D:7E:BB:A4:69:BF:B9:B1:3B:F2:58:A8:4A:EB:F0:BE:B8:06:5E:A0:BB:42:26:5E:79:3B:DD:3D:4E:44:30\r\na=setup:actpass\r\na=mid:0\r\na=extmap:1 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 urn:3gpp:video-orientation\r\na=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\na=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\na=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\na=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=sendrecv\r\na=msid:077a17e7-aec0-4aec-bef9-da6e02d3458f 593c4b1b-5b4b-4bed-b64a-2da1011de475\r\na=rtcp-mux\r\na=rtcp-rsize\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 transport-cc\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 nack pli\r\na=rtpmap:97 rtx/90000\r\na=fmtp:97 apt=96\r\na=rtpmap:103 H264/90000\r\na=rtcp-fb:103 goog-remb\r\na=rtcp-fb:103 transport-cc\r\na=rtcp-fb:103 ccm fir\r\na=rtcp-fb:103 nack\r\na=rtcp-fb:103 nack pli\r\na=fmtp:103 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f\r\na=rtpmap:104 rtx/90000\r\na=fmtp:104 apt=103\r\na=rtpmap:107 H264/90000\r\na=rtcp-fb:107 goog-remb\r\na=rtcp-fb:107 transport-cc\r\na=rtcp-fb:107 ccm fir\r\na=rtcp-fb:107 nack\r\na=rtcp-fb:107 nack pli\r\na=fmtp:107 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f\r\na=rtpmap:108 rtx/90000\r\na=fmtp:108 apt=107\r\na=rtpmap:109 H264/90000\r\na=rtcp-fb:109 goog-remb\r\na=rtcp-fb:109 transport-cc\r\na=rtcp-fb:109 ccm fir\r\na=rtcp-fb:109 nack\r\na=rtcp-fb:109 nack pli\r\na=fmtp:109 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\na=rtpmap:114 rtx/90000\r\na=fmtp:114 apt=109\r\na=rtpmap:115 H264/90000\r\na=rtcp-fb:115 goog-remb\r\na=rtcp-fb:115 transport-cc\r\na=rtcp-fb:115 ccm fir\r\na=rtcp-fb:115 nack\r\na=rtcp-fb:115 nack pli\r\na=fmtp:115 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f\r\na=rtpmap:116 rtx/90000\r\na=fmtp:116 apt=115\r\na=rtpmap:117 H264/90000\r\na=rtcp-fb:117 goog-remb\r\na=rtcp-fb:117 transport-cc\r\na=rtcp-fb:117 ccm fir\r\na=rtcp-fb:117 nack\r\na=rtcp-fb:117 nack pli\r\na=fmtp:117 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f\r\na=rtpmap:118 rtx/90000\r\na=fmtp:118 apt=117\r\na=rtpmap:39 H264/90000\r\na=rtcp-fb:39 goog-remb\r\na=rtcp-fb:39 transport-cc\r\na=rtcp-fb:39 ccm fir\r\na=rtcp-fb:39 nack\r\na=rtcp-fb:39 nack pli\r\na=fmtp:39 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f\r\na=rtpmap:40 rtx/90000\r\na=fmtp:40 apt=39\r\na=rtpmap:45 AV1/90000\r\na=rtcp-fb:45 goog-remb\r\na=rtcp-fb:45 transport-cc\r\na=rtcp-fb:45 ccm fir\r\na=rtcp-fb:45 nack\r\na=rtcp-fb:45 nack pli\r\na=fmtp:45 level-idx=5;profile=0;tier=0\r\na=rtpmap:46 rtx/90000\r\na=fmtp:46 apt=45\r\na=rtpmap:98 VP9/90000\r\na=rtcp-fb:98 goog-remb\r\na=rtcp-fb:98 transport-cc\r\na=rtcp-fb:98 ccm fir\r\na=rtcp-fb:98 nack\r\na=rtcp-fb:98 nack pli\r\na=fmtp:98 profile-id=0\r\na=rtpmap:99 rtx/90000\r\na=fmtp:99 apt=98\r\na=rtpmap:100 VP9/90000\r\na=rtcp-fb:100 goog-remb\r\na=rtcp-fb:100 transport-cc\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 nack pli\r\na=fmtp:100 profile-id=2\r\na=rtpmap:101 rtx/90000\r\na=fmtp:101 apt=100\r\na=rtpmap:119 red/90000\r\na=rtpmap:120 rtx/90000\r\na=fmtp:120 apt=119\r\na=rtpmap:121 ulpfec/90000\r\na=ssrc-group:FID 1810451430 2342397672\r\na=ssrc:1810451430 cname:NKFwP7Fvk/3zlXSL\r\na=ssrc:1810451430 msid:077a17e7-aec0-4aec-bef9-da6e02d3458f 593c4b1b-5b4b-4bed-b64a-2da1011de475\r\na=ssrc:2342397672 cname:NKFwP7Fvk/3zlXSL\r\na=ssrc:2342397672 msid:077a17e7-aec0-4aec-bef9-da6e02d3458f 593c4b1b-5b4b-4bed-b64a-2da1011de475\r\n", +}; function IntegrationView(props) { const title = createMemo(() => - integration && typeof integration === "function" + integration() && typeof integration === "function" ? integration().getName() : "Integration" ); + + async function getSDP() { + let configuration = { + iceServers: [], + }; + let mediaTrack = await navigator.mediaDevices.getUserMedia({ + audio: true, + video: false, + }); + let rtcPeerConnection = new RTCPeerConnection(configuration); + mediaTrack + .getTracks() + .forEach((track) => rtcPeerConnection.addTrack(track, mediaTrack)); + rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(offer)); + let response = await rtcPeerConnection.createAnswer(); + console.log(JSON.stringify(response)); + } + return (
Integration

{title}

+
); }