mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-13 20:11:19 -04:00
copy-paste rendezvous works again, but with new interface allowing seamless recovery for the first time
This commit is contained in:
parent
fedb124313
commit
02562ba750
3 changed files with 122 additions and 87 deletions
|
@ -1,15 +1,26 @@
|
||||||
// WebRTC Rendezvous requires the exchange of SessionDescriptions between
|
// WebRTC rendezvous requires the exchange of SessionDescriptions between
|
||||||
// peers. This file contains the domain-fronted HTTP signaling mechanism
|
// peers in order to establish a PeerConnection.
|
||||||
// between the client and a desired Broker.
|
//
|
||||||
|
// This file contains the two methods currently available to Snowflake:
|
||||||
|
//
|
||||||
|
// - Domain-fronted HTTP signaling. The Broker automatically exchange offers
|
||||||
|
// and answers between this client and some remote WebRTC proxy.
|
||||||
|
// (This is the recommended default, enabled via the flags in "torrc".)
|
||||||
|
//
|
||||||
|
// - Manual copy-paste signaling. User must create a signaling pipe.
|
||||||
|
// (The flags in torrc-manual allow this)
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/keroserene/go-webrtc"
|
"github.com/keroserene/go-webrtc"
|
||||||
)
|
)
|
||||||
|
@ -100,3 +111,98 @@ func (bc *BrokerChannel) Negotiate(offer *webrtc.SessionDescription) (
|
||||||
return nil, errors.New(BrokerErrorUnexpected)
|
return nil, errors.New(BrokerErrorUnexpected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implements the |Tongue| interface to catch snowflakes, using BrokerChannel.
|
||||||
|
type WebRTCDialer struct {
|
||||||
|
*BrokerChannel
|
||||||
|
webrtcConfig *webrtc.Configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWebRTCDialer(
|
||||||
|
broker *BrokerChannel, iceServers IceServerList) *WebRTCDialer {
|
||||||
|
config := webrtc.NewConfiguration(iceServers...)
|
||||||
|
return &WebRTCDialer{
|
||||||
|
BrokerChannel: broker,
|
||||||
|
webrtcConfig: config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize a WebRTC Connection by signaling through the broker.
|
||||||
|
func (w WebRTCDialer) Catch() (*webRTCConn, error) {
|
||||||
|
if nil == w.BrokerChannel {
|
||||||
|
return nil, errors.New("Cannot Dial WebRTC without a BrokerChannel.")
|
||||||
|
}
|
||||||
|
// TODO: [#3] Fetch ICE server information from Broker.
|
||||||
|
// TODO: [#18] Consider TURN servers here too.
|
||||||
|
connection := NewWebRTCConnection(w.webrtcConfig, w.BrokerChannel)
|
||||||
|
err := connection.Connect()
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyPasteDialer handles the interaction required to copy-paste the
|
||||||
|
// offers and answers.
|
||||||
|
// Implements |Tongue| interface to catch snowflakes manually.
|
||||||
|
// Supports recovery of connections.
|
||||||
|
type CopyPasteDialer struct {
|
||||||
|
webrtcConfig *webrtc.Configuration
|
||||||
|
signal *os.File
|
||||||
|
current *webRTCConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCopyPasteDialer(iceServers IceServerList) *CopyPasteDialer {
|
||||||
|
log.Println("No HTTP signaling detected. Using manual copy-paste signaling.")
|
||||||
|
log.Println("Waiting for a \"signal\" pipe...")
|
||||||
|
// This FIFO receives signaling messages.
|
||||||
|
err := syscall.Mkfifo("signal", 0600)
|
||||||
|
if err != nil {
|
||||||
|
if syscall.EEXIST != err.(syscall.Errno) {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
signalFile, err := os.OpenFile("signal", os.O_RDONLY, 0600)
|
||||||
|
if nil != err {
|
||||||
|
log.Fatal(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
config := webrtc.NewConfiguration(iceServers...)
|
||||||
|
dialer := &CopyPasteDialer{
|
||||||
|
webrtcConfig: config,
|
||||||
|
signal: signalFile,
|
||||||
|
}
|
||||||
|
go dialer.readSignals()
|
||||||
|
return dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize a WebRTC connection via manual copy-paste.
|
||||||
|
func (d *CopyPasteDialer) Catch() (*webRTCConn, error) {
|
||||||
|
if nil == d.signal {
|
||||||
|
return nil, errors.New("Cannot copy-paste dial without signal pipe.")
|
||||||
|
}
|
||||||
|
connection := NewWebRTCConnection(d.webrtcConfig, nil)
|
||||||
|
// Must keep track of pending new connection until copy-paste completes.
|
||||||
|
d.current = connection
|
||||||
|
// Outputs SDP offer to log, expecting user to copy-paste to the remote Peer.
|
||||||
|
// Blocks until user pastes back the answer.
|
||||||
|
err := connection.Connect()
|
||||||
|
d.current = nil
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manual copy-paste signalling.
|
||||||
|
func (d *CopyPasteDialer) readSignals() {
|
||||||
|
defer d.signal.Close()
|
||||||
|
log.Printf("CopyPasteDialer: reading messages from signal pipe.")
|
||||||
|
s := bufio.NewScanner(d.signal)
|
||||||
|
for s.Scan() {
|
||||||
|
msg := s.Text()
|
||||||
|
sdp := webrtc.DeserializeSessionDescription(msg)
|
||||||
|
if sdp == nil {
|
||||||
|
log.Printf("CopyPasteDialer: ignoring invalid signal message %+q", msg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
d.current.answerChannel <- sdp
|
||||||
|
}
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
log.Printf("signal FIFO: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"io"
|
"io"
|
||||||
|
@ -110,45 +109,6 @@ func handler(socks SocksConnector, snowflakes SnowflakeCollector) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupCopyPaste() {
|
|
||||||
log.Println("No HTTP signaling detected. Waiting for a \"signal\" pipe...")
|
|
||||||
// This FIFO receives signaling messages.
|
|
||||||
err := syscall.Mkfifo("signal", 0600)
|
|
||||||
if err != nil {
|
|
||||||
if syscall.EEXIST != err.(syscall.Errno) {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
signalFile, err := os.OpenFile("signal", os.O_RDONLY, 0600)
|
|
||||||
if nil != err {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer signalFile.Close()
|
|
||||||
go readSignalingMessages(signalFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manual copy-paste signalling.
|
|
||||||
// TODO: Needs fix since multiplexing changes access to the remotes.
|
|
||||||
func readSignalingMessages(f *os.File) {
|
|
||||||
log.Printf("readSignalingMessages")
|
|
||||||
s := bufio.NewScanner(f)
|
|
||||||
for s.Scan() {
|
|
||||||
msg := s.Text()
|
|
||||||
log.Printf("readSignalingMessages loop %+q", msg)
|
|
||||||
sdp := webrtc.DeserializeSessionDescription(msg)
|
|
||||||
if sdp == nil {
|
|
||||||
log.Printf("ignoring invalid signal message %+q", msg)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// webrtcRemotes[0].answerChannel <- sdp
|
|
||||||
}
|
|
||||||
log.Printf("close answerChannel")
|
|
||||||
// close(webrtcRemotes[0].answerChannel)
|
|
||||||
if err := s.Err(); err != nil {
|
|
||||||
log.Printf("signal FIFO: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
webrtc.SetLoggingVerbosity(1)
|
webrtc.SetLoggingVerbosity(1)
|
||||||
logFile, err := os.OpenFile("snowflake.log",
|
logFile, err := os.OpenFile("snowflake.log",
|
||||||
|
@ -169,24 +129,24 @@ func main() {
|
||||||
"capacity for number of multiplexed WebRTC peers")
|
"capacity for number of multiplexed WebRTC peers")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// TODO: Maybe just get rid of copy-paste option entirely.
|
// Prepare to collect remote WebRTC peers.
|
||||||
if "" == *brokerURL {
|
|
||||||
setupCopyPaste()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare WebRTC SnowflakeCollector, Broker, then accumulate connections.
|
|
||||||
snowflakes := NewPeers(*max)
|
snowflakes := NewPeers(*max)
|
||||||
|
if "" != *brokerURL {
|
||||||
|
// Use potentially domain-fronting broker to rendezvous.
|
||||||
broker := NewBrokerChannel(*brokerURL, *frontDomain, CreateBrokerTransport())
|
broker := NewBrokerChannel(*brokerURL, *frontDomain, CreateBrokerTransport())
|
||||||
snowflakes.Tongue = NewWebRTCDialer(broker, iceServers)
|
snowflakes.Tongue = NewWebRTCDialer(broker, iceServers)
|
||||||
|
} else {
|
||||||
// Use a real logger for traffic.
|
// Otherwise, use manual copy and pasting of SDP messages.
|
||||||
|
snowflakes.Tongue = NewCopyPasteDialer(iceServers)
|
||||||
|
}
|
||||||
|
// Use a real logger to periodically output how much traffic is happening.
|
||||||
snowflakes.BytesLogger = &BytesSyncLogger{
|
snowflakes.BytesLogger = &BytesSyncLogger{
|
||||||
inboundChan: make(chan int, 5), outboundChan: make(chan int, 5),
|
inboundChan: make(chan int, 5), outboundChan: make(chan int, 5),
|
||||||
inbound: 0, outbound: 0, inEvents: 0, outEvents: 0,
|
inbound: 0, outbound: 0, inEvents: 0, outEvents: 0,
|
||||||
}
|
}
|
||||||
|
go snowflakes.BytesLogger.Log()
|
||||||
|
|
||||||
go ConnectLoop(snowflakes)
|
go ConnectLoop(snowflakes)
|
||||||
go snowflakes.BytesLogger.Log()
|
|
||||||
|
|
||||||
// Begin goptlib client process.
|
// Begin goptlib client process.
|
||||||
ptInfo, err := pt.ClientSetup(nil)
|
ptInfo, err := pt.ClientSetup(nil)
|
||||||
|
@ -197,7 +157,6 @@ func main() {
|
||||||
pt.ProxyError("proxy is not supported")
|
pt.ProxyError("proxy is not supported")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
listeners := make([]net.Listener, 0)
|
listeners := make([]net.Listener, 0)
|
||||||
for _, methodName := range ptInfo.MethodNames {
|
for _, methodName := range ptInfo.MethodNames {
|
||||||
switch methodName {
|
switch methodName {
|
||||||
|
@ -234,9 +193,7 @@ func main() {
|
||||||
for _, ln := range listeners {
|
for _, ln := range listeners {
|
||||||
ln.Close()
|
ln.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
snowflakes.End()
|
snowflakes.End()
|
||||||
|
|
||||||
// wait for second signal or no more handlers
|
// wait for second signal or no more handlers
|
||||||
sig = nil
|
sig = nil
|
||||||
for sig == nil && numHandlers != 0 {
|
for sig == nil && numHandlers != 0 {
|
||||||
|
|
|
@ -11,34 +11,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Implements the |Tongue| interface to catch snowflakes, using a BrokerChannel.
|
|
||||||
type WebRTCDialer struct {
|
|
||||||
*BrokerChannel
|
|
||||||
webrtcConfig *webrtc.Configuration
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWebRTCDialer(
|
|
||||||
broker *BrokerChannel, iceServers IceServerList) *WebRTCDialer {
|
|
||||||
|
|
||||||
config := webrtc.NewConfiguration(iceServers...)
|
|
||||||
return &WebRTCDialer{
|
|
||||||
BrokerChannel: broker,
|
|
||||||
webrtcConfig: config,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize a WebRTC Connection by signaling through the broker.
|
|
||||||
func (w WebRTCDialer) Catch() (*webRTCConn, error) {
|
|
||||||
if nil == w.BrokerChannel {
|
|
||||||
return nil, errors.New("Cannot Dial WebRTC without a BrokerChannel.")
|
|
||||||
}
|
|
||||||
// TODO: [#3] Fetch ICE server information from Broker.
|
|
||||||
// TODO: [#18] Consider TURN servers here too.
|
|
||||||
connection := NewWebRTCConnection(w.webrtcConfig, w.BrokerChannel)
|
|
||||||
err := connection.Connect()
|
|
||||||
return connection, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remote WebRTC peer. Implements the |net.Conn| interface.
|
// Remote WebRTC peer. Implements the |net.Conn| interface.
|
||||||
type webRTCConn struct {
|
type webRTCConn struct {
|
||||||
config *webrtc.Configuration
|
config *webrtc.Configuration
|
||||||
|
@ -282,11 +254,11 @@ func (c *webRTCConn) sendOfferToBroker() {
|
||||||
func (c *webRTCConn) exchangeSDP() error {
|
func (c *webRTCConn) exchangeSDP() error {
|
||||||
select {
|
select {
|
||||||
case offer := <-c.offerChannel:
|
case offer := <-c.offerChannel:
|
||||||
// Display for copy-paste, when no broker available.
|
// Display for copy-paste when no broker available.
|
||||||
if nil == c.broker {
|
if nil == c.broker {
|
||||||
log.Printf("Please Copy & Paste the following to the peer:")
|
log.Printf("Please Copy & Paste the following to the peer:")
|
||||||
log.Printf("----------------")
|
log.Printf("----------------")
|
||||||
log.Printf("\n" + offer.Serialize() + "\n")
|
log.Printf("\n\n" + offer.Serialize() + "\n\n")
|
||||||
log.Printf("----------------")
|
log.Printf("----------------")
|
||||||
}
|
}
|
||||||
case err := <-c.errorChannel:
|
case err := <-c.errorChannel:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue