char/live.umm.gay

git clone https://git.t4t.associates/char/live.umm.gay

videogame hackerEnable IPv6 for ICEacb190d

main
8.0 KiB328 linesraw
1package main
2
3import (
4	"errors"
5	"fmt"
6	"io"
7	"log"
8	"net"
9	"net/http"
10	"os"
11	"strconv"
12	"strings"
13	"sync"
14
15	"database/sql"
16
17	"github.com/joho/godotenv"
18	"github.com/pion/ice/v2"
19	"github.com/pion/interceptor"
20	"github.com/pion/webrtc/v3"
21
22	_ "github.com/mattn/go-sqlite3"
23)
24
25type WebRTCStream struct {
26	audioTrack, videoTrack *webrtc.TrackLocalStaticRTP
27}
28
29var (
30	runningStreams     map[string]WebRTCStream
31	runningStreamsLock sync.Mutex
32	api                *webrtc.API
33	db                 *sql.DB
34)
35
36func main() {
37	if err := godotenv.Load(".env"); err != nil {
38		log.Fatal(err)
39	}
40
41	db = setupDatabase()
42	defer db.Close()
43
44	api = setupWebRTC()
45	runningStreams = map[string]WebRTCStream{}
46
47	mux := http.NewServeMux()
48	mux.HandleFunc("/api/wish-server/whip", withCors(HandleWHIP))
49	mux.HandleFunc("/api/wish-server/whep", withCors(HandleWHEP))
50
51	log.Fatal((&http.Server{
52		Handler: mux,
53		Addr:    os.Getenv("HTTP_ADDRESS"),
54	}).ListenAndServe())
55}
56
57func setupDatabase() *sql.DB {
58	db, err := sql.Open("sqlite3", os.Getenv("DATABASE_URL"))
59	if err != nil {
60		log.Fatal(err)
61	}
62
63	return db
64}
65
66func setupWebRTC() *webrtc.API {
67	mediaEngine := &webrtc.MediaEngine{}
68	if err := mediaEngine.RegisterDefaultCodecs(); err != nil {
69		log.Fatal(err)
70	}
71
72	interceptorRegistry := &interceptor.Registry{}
73	if err := webrtc.RegisterDefaultInterceptors(mediaEngine, interceptorRegistry); err != nil {
74		log.Fatal(err)
75	}
76
77	settingEngine := webrtc.SettingEngine{}
78	setupICE(&settingEngine)
79
80	return webrtc.NewAPI(
81		webrtc.WithMediaEngine(mediaEngine),
82		webrtc.WithInterceptorRegistry(interceptorRegistry),
83		webrtc.WithSettingEngine(settingEngine),
84	)
85}
86
87func setupICE(settingEngine *webrtc.SettingEngine) {
88	settingEngine.SetNetworkTypes([]webrtc.NetworkType{
89		webrtc.NetworkTypeUDP4, webrtc.NetworkTypeUDP6,
90		webrtc.NetworkTypeTCP4, webrtc.NetworkTypeTCP6,
91	})
92
93	if udpPort := os.Getenv("UDP_MUX_PORT"); udpPort != "" {
94		port, err := strconv.Atoi(udpPort)
95		if err != nil {
96			log.Fatal(err)
97		}
98		mux, err := ice.NewMultiUDPMuxFromPort(port)
99		if err != nil {
100			log.Fatal(err)
101		}
102		settingEngine.SetICEUDPMux(mux)
103	}
104
105	if tcpAddr := os.Getenv("TCP_MUX_ADDR"); tcpAddr != "" {
106		addr, err := net.ResolveTCPAddr("tcp", tcpAddr)
107		if err != nil {
108			log.Fatal(err)
109		}
110
111		listener, err := net.ListenTCP("tcp", addr)
112		if err != nil {
113			log.Fatal(err)
114		}
115
116		mux := webrtc.NewICETCPMux(nil, listener, 8)
117		settingEngine.SetICETCPMux(mux)
118	}
119}
120
121func withCors(next func(w http.ResponseWriter, r *http.Request)) http.HandlerFunc {
122	return func(res http.ResponseWriter, req *http.Request) {
123		res.Header().Set("Access-Control-Allow-Origin", "*")
124		res.Header().Set("Access-Control-Allow-Methods", "*")
125		res.Header().Set("Access-Control-Allow-Headers", "*")
126
127		if req.Method != http.MethodOptions {
128			next(res, req)
129		}
130	}
131}
132
133func logHTTPError(w http.ResponseWriter, err string, code int) {
134	log.Println(err)
135	http.Error(w, err, code)
136}
137
138func getTracksForStream(streamName string) (
139	*webrtc.TrackLocalStaticRTP,
140	*webrtc.TrackLocalStaticRTP,
141	error,
142) {
143	runningStreamsLock.Lock()
144	defer runningStreamsLock.Unlock()
145
146	foundStream, ok := runningStreams[streamName]
147	if !ok {
148		videoTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264}, "video", "pion")
149		if err != nil {
150			return nil, nil, err
151		}
152
153		audioTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus}, "audio", "pion")
154		if err != nil {
155			return nil, nil, err
156		}
157
158		foundStream = WebRTCStream{
159			audioTrack: audioTrack,
160			videoTrack: videoTrack,
161		}
162		runningStreams[streamName] = foundStream
163	}
164
165	return foundStream.audioTrack, foundStream.videoTrack, nil
166}
167
168func HandleWHIP(res http.ResponseWriter, req *http.Request) {
169	authorization := req.Header.Get("Authorization")
170	if authorization == "" {
171		logHTTPError(res, "Authorization was not set", http.StatusBadRequest)
172		return
173	}
174	streamName, streamPassword, _ := strings.Cut(authorization, ":")
175	streamName, _ = strings.CutPrefix(strings.ToLower(streamName), "bearer ")
176
177	var qN string
178	var qP string
179	if err := db.QueryRow("SELECT * FROM streams WHERE stream = ? AND password = ?", streamName, streamPassword).Scan(&qN, &qP); err != nil {
180		logHTTPError(res, "Invalid stream authorization for: "+streamName+" - "+err.Error(), http.StatusUnauthorized)
181		return
182	}
183
184	offer, err := io.ReadAll(req.Body)
185	if err != nil {
186		logHTTPError(res, err.Error(), http.StatusInternalServerError)
187		return
188	}
189
190	peerConnection, err := api.NewPeerConnection(webrtc.Configuration{
191		ICEServers: []webrtc.ICEServer{
192			{
193				URLs: []string{"stun:stun.cloudflare.com:3478"},
194			},
195			{
196				URLs: []string{"stun:stun.l.google.com:19302"},
197			},
198		},
199	})
200	if err != nil {
201		logHTTPError(res, err.Error(), http.StatusInternalServerError)
202		return
203	}
204
205	audioTrack, videoTrack, err := getTracksForStream(streamName)
206	if err != nil {
207		logHTTPError(res, err.Error(), http.StatusInternalServerError)
208		return
209	}
210
211	peerConnection.OnTrack(func(track *webrtc.TrackRemote, _recv *webrtc.RTPReceiver) {
212		var localTrack *webrtc.TrackLocalStaticRTP
213		if strings.HasPrefix(track.Codec().RTPCodecCapability.MimeType, "audio/") {
214			localTrack = audioTrack
215		} else {
216			localTrack = videoTrack
217		}
218
219		rtpBuf := make([]byte, 1500)
220		for {
221			rtpRead, _, readErr := track.Read(rtpBuf)
222			switch {
223			case errors.Is(readErr, io.EOF):
224				return
225			case readErr != nil:
226				log.Println(readErr)
227				return
228			}
229
230			if _, writeErr := localTrack.Write(rtpBuf[:rtpRead]); writeErr != nil && !errors.Is(writeErr, io.ErrClosedPipe) {
231				log.Println(writeErr)
232				return
233			}
234		}
235	})
236
237	peerConnection.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
238		if state == webrtc.ICEConnectionStateFailed {
239			if err := peerConnection.Close(); err != nil {
240				log.Println(err)
241				return
242			}
243		}
244	})
245
246	if err := peerConnection.SetRemoteDescription(webrtc.SessionDescription{
247		SDP:  string(offer),
248		Type: webrtc.SDPTypeOffer,
249	}); err != nil {
250		logHTTPError(res, err.Error(), http.StatusBadRequest)
251		return
252	}
253
254	gatheringComplete := webrtc.GatheringCompletePromise(peerConnection)
255
256	answer, err := peerConnection.CreateAnswer(nil)
257	if err != nil {
258		logHTTPError(res, err.Error(), http.StatusInternalServerError)
259		return
260	} else if err = peerConnection.SetLocalDescription(answer); err != nil {
261		logHTTPError(res, err.Error(), http.StatusInternalServerError)
262		return
263	}
264
265	<-gatheringComplete
266
267	fmt.Fprint(res, peerConnection.LocalDescription().SDP)
268}
269
270func HandleWHEP(res http.ResponseWriter, req *http.Request) {
271	streamName := req.Header.Get("Authorization")
272	streamName, _ = strings.CutPrefix(strings.ToLower(streamName), "bearer ")
273	if streamName == "" {
274		logHTTPError(res, "Stream name was not set", http.StatusBadRequest)
275		return
276	}
277
278	offer, err := io.ReadAll(req.Body)
279	if err != nil {
280		logHTTPError(res, err.Error(), http.StatusInternalServerError)
281		return
282	}
283
284	peerConnection, err := api.NewPeerConnection(webrtc.Configuration{})
285	if err != nil {
286		logHTTPError(res, err.Error(), http.StatusInternalServerError)
287		return
288	}
289
290	audioTrack, videoTrack, err := getTracksForStream(streamName)
291	if err != nil {
292		logHTTPError(res, err.Error(), http.StatusInternalServerError)
293		return
294	}
295
296	if _, err = peerConnection.AddTrack(audioTrack); err != nil {
297		logHTTPError(res, err.Error(), http.StatusInternalServerError)
298		return
299	}
300
301	if _, err = peerConnection.AddTrack(videoTrack); err != nil {
302		logHTTPError(res, err.Error(), http.StatusInternalServerError)
303		return
304	}
305
306	if err := peerConnection.SetRemoteDescription(webrtc.SessionDescription{
307		SDP:  string(offer),
308		Type: webrtc.SDPTypeOffer,
309	}); err != nil {
310		logHTTPError(res, err.Error(), http.StatusInternalServerError)
311		return
312	}
313
314	gatheringComplete := webrtc.GatheringCompletePromise(peerConnection)
315
316	answer, err := peerConnection.CreateAnswer(nil)
317	if err != nil {
318		logHTTPError(res, err.Error(), http.StatusInternalServerError)
319		return
320	} else if err = peerConnection.SetLocalDescription(answer); err != nil {
321		logHTTPError(res, err.Error(), http.StatusInternalServerError)
322		return
323	}
324
325	<-gatheringComplete
326
327	fmt.Fprint(res, peerConnection.LocalDescription().SDP)
328}