Validate SDP offers and answers

This commit is contained in:
itchyonion 2023-03-14 12:39:57 -07:00
parent 8e5ea82611
commit 07b5f07452
No known key found for this signature in database
GPG key ID: 4B87B720348500EA
3 changed files with 56 additions and 13 deletions

View file

@ -1,7 +1,9 @@
package main package main
import ( import (
"bytes"
"errors" "errors"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
@ -137,10 +139,17 @@ func clientOffers(i *IPC, w http.ResponseWriter, r *http.Request) {
return return
} }
err = validateSDP(body)
if err != nil {
log.Println("Error client SDP: ", err.Error())
w.WriteHeader(http.StatusBadRequest)
return
}
// Handle the legacy version // Handle the legacy version
// //
// We support two client message formats. The legacy format is for backwards // We support two client message formats. The legacy format is for backwards
// combatability and relies heavily on HTTP headers and status codes to convey // compatability and relies heavily on HTTP headers and status codes to convey
// information. // information.
isLegacy := false isLegacy := false
if len(body) > 0 && body[0] == '{' { if len(body) > 0 && body[0] == '{' {
@ -197,7 +206,7 @@ func clientOffers(i *IPC, w http.ResponseWriter, r *http.Request) {
} }
/* /*
Expects snowflake proxes which have previously successfully received Expects snowflake proxies which have previously successfully received
an offer from proxyHandler to respond with an answer in an HTTP POST, an offer from proxyHandler to respond with an answer in an HTTP POST,
which the broker will pass back to the original client. which the broker will pass back to the original client.
*/ */
@ -209,6 +218,13 @@ func proxyAnswers(i *IPC, w http.ResponseWriter, r *http.Request) {
return return
} }
err = validateSDP(body)
if err != nil {
log.Println("Error proxy SDP: ", err.Error())
w.WriteHeader(http.StatusBadRequest)
return
}
arg := messages.Arg{ arg := messages.Arg{
Body: body, Body: body,
RemoteAddr: "", RemoteAddr: "",
@ -233,3 +249,12 @@ func proxyAnswers(i *IPC, w http.ResponseWriter, r *http.Request) {
log.Printf("proxyAnswers unable to write answer response with error: %v", err) log.Printf("proxyAnswers unable to write answer response with error: %v", err)
} }
} }
func validateSDP(SDP []byte) error {
// TODO: more validation likely needed
if !bytes.Contains(SDP, []byte("a=candidate")) {
return fmt.Errorf("SDP contains no candidate")
}
return nil
}

View file

@ -4,8 +4,10 @@ import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt"
"io" "io"
"log" "log"
"strings"
"sync" "sync"
"time" "time"
@ -143,10 +145,10 @@ func (c *WebRTCPeer) checkForStaleness(timeout time.Duration) {
} }
} }
// connect does the bulk of the work: gather ICE candidates, send the SDP offer to broker,
// receive an answer from broker, and wait for data channel to open
func (c *WebRTCPeer) connect(config *webrtc.Configuration, broker *BrokerChannel) error { func (c *WebRTCPeer) connect(config *webrtc.Configuration, broker *BrokerChannel) error {
log.Println(c.id, " connecting...") log.Println(c.id, " connecting...")
// TODO: When go-webrtc is more stable, it's possible that a new
// PeerConnection won't need to be re-prepared each time.
err := c.preparePeerConnection(config) err := c.preparePeerConnection(config)
localDescription := c.pc.LocalDescription() localDescription := c.pc.LocalDescription()
c.eventsLogger.OnNewSnowflakeEvent(event.EventOnOfferCreated{ c.eventsLogger.OnNewSnowflakeEvent(event.EventOnOfferCreated{
@ -187,7 +189,7 @@ func (c *WebRTCPeer) connect(config *webrtc.Configuration, broker *BrokerChannel
} }
// preparePeerConnection creates a new WebRTC PeerConnection and returns it // preparePeerConnection creates a new WebRTC PeerConnection and returns it
// after ICE candidate gathering is complete.. // after non-trickle ICE candidate gathering is complete.
func (c *WebRTCPeer) preparePeerConnection(config *webrtc.Configuration) error { func (c *WebRTCPeer) preparePeerConnection(config *webrtc.Configuration) error {
var err error var err error
s := webrtc.SettingEngine{} s := webrtc.SettingEngine{}
@ -240,10 +242,8 @@ func (c *WebRTCPeer) preparePeerConnection(config *webrtc.Configuration) error {
}) })
c.transport = dc c.transport = dc
c.open = make(chan struct{}) c.open = make(chan struct{})
log.Println("WebRTC: DataChannel created.") log.Println("WebRTC: DataChannel created")
// Allow candidates to accumulate until ICEGatheringStateComplete.
done := webrtc.GatheringCompletePromise(c.pc)
offer, err := c.pc.CreateOffer(nil) offer, err := c.pc.CreateOffer(nil)
// TODO: Potentially timeout and retry if ICE isn't working. // TODO: Potentially timeout and retry if ICE isn't working.
if err != nil { if err != nil {
@ -252,16 +252,23 @@ func (c *WebRTCPeer) preparePeerConnection(config *webrtc.Configuration) error {
return err return err
} }
log.Println("WebRTC: Created offer") log.Println("WebRTC: Created offer")
// Allow candidates to accumulate until ICEGatheringStateComplete.
done := webrtc.GatheringCompletePromise(c.pc)
// Start gathering candidates
err = c.pc.SetLocalDescription(offer) err = c.pc.SetLocalDescription(offer)
if err != nil { if err != nil {
log.Println("Failed to prepare offer", err) log.Println("Failed to apply offer", err)
c.pc.Close() c.pc.Close()
return err return err
} }
log.Println("WebRTC: Set local description") log.Println("WebRTC: Set local description")
<-done // Wait for ICE candidate gathering to complete. <-done // Wait for ICE candidate gathering to complete.
log.Println("WebRTC: PeerConnection created.")
if !strings.Contains(c.pc.LocalDescription().SDP, "\na=candidate:") {
return fmt.Errorf("SDP offer contains no candidate")
}
return nil return nil
} }

View file

@ -250,6 +250,8 @@ func (s *SignalingServer) pollOffer(sid string, proxyType string, acceptedRelayP
return nil, "" return nil, ""
} }
// sendAnswer encodes an SDP answer, sends it to the broker
// and wait for its response
func (s *SignalingServer) sendAnswer(sid string, pc *webrtc.PeerConnection) error { func (s *SignalingServer) sendAnswer(sid string, pc *webrtc.PeerConnection) error {
ld := pc.LocalDescription() ld := pc.LocalDescription()
if !s.keepLocalAddresses { if !s.keepLocalAddresses {
@ -485,6 +487,10 @@ func (sf *SnowflakeProxy) makePeerConnectionFromOffer(sdp *webrtc.SessionDescrip
// Wait for ICE candidate gathering to complete // Wait for ICE candidate gathering to complete
<-done <-done
if !strings.Contains(pc.LocalDescription().SDP, "\na=candidate:") {
return nil, fmt.Errorf("SDP answer contains no candidate")
}
log.Printf("Answer: \n\t%s", strings.ReplaceAll(pc.LocalDescription().SDP, "\n", "\n\t")) log.Printf("Answer: \n\t%s", strings.ReplaceAll(pc.LocalDescription().SDP, "\n", "\n\t"))
return pc, nil return pc, nil
@ -524,15 +530,16 @@ func (sf *SnowflakeProxy) makeNewPeerConnection(config webrtc.Configuration,
pc.Close() pc.Close()
return nil, err return nil, err
} }
log.Println("Probetest: Creating offer") log.Println("Probetest: Created Offer")
// As of v3.0.0, pion-webrtc uses trickle ICE by default. // As of v3.0.0, pion-webrtc uses trickle ICE by default.
// We have to wait for candidate gathering to complete // We have to wait for candidate gathering to complete
// before we send the offer // before we send the offer
done := webrtc.GatheringCompletePromise(pc) done := webrtc.GatheringCompletePromise(pc)
// start the gathering of ICE candidates
err = pc.SetLocalDescription(offer) err = pc.SetLocalDescription(offer)
if err != nil { if err != nil {
log.Println("Failed to prepare offer", err) log.Println("Failed to apply offer", err)
pc.Close() pc.Close()
return nil, err return nil, err
} }
@ -540,6 +547,11 @@ func (sf *SnowflakeProxy) makeNewPeerConnection(config webrtc.Configuration,
// Wait for ICE candidate gathering to complete // Wait for ICE candidate gathering to complete
<-done <-done
if !strings.Contains(pc.LocalDescription().SDP, "\na=candidate:") {
return nil, fmt.Errorf("Probetest SDP offer contains no candidate")
}
return pc, nil return pc, nil
} }
@ -701,7 +713,6 @@ func (sf *SnowflakeProxy) checkNATType(config webrtc.Configuration, probeURL str
log.Printf("Error parsing url: %s", err.Error()) log.Printf("Error parsing url: %s", err.Error())
} }
// create offer used for probetest
dataChan := make(chan struct{}) dataChan := make(chan struct{})
pc, err := sf.makeNewPeerConnection(config, dataChan) pc, err := sf.makeNewPeerConnection(config, dataChan)
if err != nil { if err != nil {