Compare commits

...

1 Commits

Author SHA1 Message Date
cb772b46e3 working: turn server websocket 2025-03-24 20:25:05 +01:00
6 changed files with 192 additions and 1 deletions

7
go.mod
View File

@ -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

14
go.sum
View File

@ -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=

View File

@ -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()
}

92
server/turn_server.go Normal file
View File

@ -0,0 +1,92 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// 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
}

38
server/websocket.go Normal file
View File

@ -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
}

View File

@ -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 (
<div
class={
@ -18,6 +40,9 @@ function IntegrationView(props) {
>
<span>Integration</span>
<h1>{title}</h1>
<button class="btn btn-primary" onClick={() => getSDP()}>
get SDP
</button>
</div>
);
}