mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-13 11:11:30 -04:00
Makes BrokerChannel abstract over a rendezvousMethod. BrokerChannel itself is responsible for keepLocalAddresses and the NAT type state, as well as encoding and decoding client poll messages. rendezvousMethod is only responsible for delivery of encoded messages.
166 lines
5.1 KiB
Go
166 lines
5.1 KiB
Go
// WebRTC rendezvous requires the exchange of SessionDescriptions between
|
|
// peers in order to establish a PeerConnection.
|
|
//
|
|
// This file contains the one method currently available to Snowflake:
|
|
//
|
|
// - Domain-fronted HTTP signaling. The Broker automatically exchange offers
|
|
// and answers between this client and some remote WebRTC proxy.
|
|
|
|
package lib
|
|
|
|
import (
|
|
"errors"
|
|
"log"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
"git.torproject.org/pluggable-transports/snowflake.git/common/messages"
|
|
"git.torproject.org/pluggable-transports/snowflake.git/common/nat"
|
|
"git.torproject.org/pluggable-transports/snowflake.git/common/util"
|
|
"github.com/pion/webrtc/v3"
|
|
)
|
|
|
|
const (
|
|
BrokerErrorUnexpected string = "Unexpected error, no answer."
|
|
readLimit = 100000 //Maximum number of bytes to be read from an HTTP response
|
|
)
|
|
|
|
// rendezvousMethod represents a way of communicating with the broker: sending
|
|
// an encoded client poll request (SDP offer) and receiving an encoded client
|
|
// poll response (SDP answer) in return. rendezvousMethod is used by
|
|
// BrokerChannel, which is in charge of encoding and decoding, and all other
|
|
// tasks that are independent of the rendezvous method.
|
|
type rendezvousMethod interface {
|
|
Exchange([]byte) ([]byte, error)
|
|
}
|
|
|
|
// BrokerChannel contains a rendezvousMethod, as well as data that is not
|
|
// specific to any rendezvousMethod. BrokerChannel has the responsibility of
|
|
// encoding and decoding SDP offers and answers; rendezvousMethod is responsible
|
|
// for the exchange of encoded information.
|
|
type BrokerChannel struct {
|
|
rendezvous rendezvousMethod
|
|
keepLocalAddresses bool
|
|
NATType string
|
|
lock sync.Mutex
|
|
}
|
|
|
|
// We make a copy of DefaultTransport because we want the default Dial
|
|
// and TLSHandshakeTimeout settings. But we want to disable the default
|
|
// ProxyFromEnvironment setting.
|
|
func CreateBrokerTransport() http.RoundTripper {
|
|
transport := http.DefaultTransport.(*http.Transport)
|
|
transport.Proxy = nil
|
|
transport.ResponseHeaderTimeout = 15 * time.Second
|
|
return transport
|
|
}
|
|
|
|
// Construct a new BrokerChannel, where:
|
|
// |broker| is the full URL of the facilitating program which assigns proxies
|
|
// to clients, and |front| is the option fronting domain.
|
|
func NewBrokerChannel(broker string, front string, transport http.RoundTripper, keepLocalAddresses bool) (*BrokerChannel, error) {
|
|
log.Println("Rendezvous using Broker at:", broker)
|
|
if front != "" {
|
|
log.Println("Domain fronting using:", front)
|
|
}
|
|
|
|
rendezvous, err := newHTTPRendezvous(broker, front, transport)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &BrokerChannel{
|
|
rendezvous: rendezvous,
|
|
keepLocalAddresses: keepLocalAddresses,
|
|
NATType: nat.NATUnknown,
|
|
}, nil
|
|
}
|
|
|
|
// Roundtrip HTTP POST using WebRTC SessionDescriptions.
|
|
//
|
|
// Send an SDP offer to the broker, which assigns a proxy and responds
|
|
// with an SDP answer from a designated remote WebRTC peer.
|
|
func (bc *BrokerChannel) Negotiate(offer *webrtc.SessionDescription) (
|
|
*webrtc.SessionDescription, error) {
|
|
// Ideally, we could specify an `RTCIceTransportPolicy` that would handle
|
|
// this for us. However, "public" was removed from the draft spec.
|
|
// See https://developer.mozilla.org/en-US/docs/Web/API/RTCConfiguration#RTCIceTransportPolicy_enum
|
|
if !bc.keepLocalAddresses {
|
|
offer = &webrtc.SessionDescription{
|
|
Type: offer.Type,
|
|
SDP: util.StripLocalAddresses(offer.SDP),
|
|
}
|
|
}
|
|
offerSDP, err := util.SerializeSessionDescription(offer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Encode the client poll request.
|
|
bc.lock.Lock()
|
|
req := &messages.ClientPollRequest{
|
|
Offer: offerSDP,
|
|
NAT: bc.NATType,
|
|
}
|
|
encReq, err := req.EncodePollRequest()
|
|
bc.lock.Unlock()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Do the exchange using our rendezvousMethod.
|
|
encResp, err := bc.rendezvous.Exchange(encReq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
log.Printf("Received answer: %s", string(encResp))
|
|
|
|
// Decode the client poll response.
|
|
resp, err := messages.DecodeClientPollResponse(encResp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp.Error != "" {
|
|
return nil, errors.New(resp.Error)
|
|
}
|
|
return util.DeserializeSessionDescription(resp.Answer)
|
|
}
|
|
|
|
func (bc *BrokerChannel) SetNATType(NATType string) {
|
|
bc.lock.Lock()
|
|
bc.NATType = NATType
|
|
bc.lock.Unlock()
|
|
log.Printf("NAT Type: %s", NATType)
|
|
}
|
|
|
|
// Implements the |Tongue| interface to catch snowflakes, using BrokerChannel.
|
|
type WebRTCDialer struct {
|
|
*BrokerChannel
|
|
webrtcConfig *webrtc.Configuration
|
|
max int
|
|
}
|
|
|
|
func NewWebRTCDialer(broker *BrokerChannel, iceServers []webrtc.ICEServer, max int) *WebRTCDialer {
|
|
config := webrtc.Configuration{
|
|
ICEServers: iceServers,
|
|
}
|
|
|
|
return &WebRTCDialer{
|
|
BrokerChannel: broker,
|
|
webrtcConfig: &config,
|
|
max: max,
|
|
}
|
|
}
|
|
|
|
// Initialize a WebRTC Connection by signaling through the broker.
|
|
func (w WebRTCDialer) Catch() (*WebRTCPeer, error) {
|
|
// TODO: [#25591] Fetch ICE server information from Broker.
|
|
// TODO: [#25596] Consider TURN servers here too.
|
|
return NewWebRTCPeer(w.webrtcConfig, w.BrokerChannel)
|
|
}
|
|
|
|
// Returns the maximum number of snowflakes to collect
|
|
func (w WebRTCDialer) GetMax() int {
|
|
return w.max
|
|
}
|