Rough version of a Tor-compatible client transport plugin

This commit is contained in:
David Fifield 2016-01-04 16:37:15 -08:00 committed by Arlo Breault
parent 948218d76b
commit aa46a58e3e
3 changed files with 359 additions and 182 deletions

View file

@ -4,7 +4,15 @@ A Pluggable Transport using WebRTC
### Usage ### Usage
- `go build webrtc-client.go` Open up four terminals:
- `tor -f torrc`
1. tor -f torrc SOCKSPort auto
2. tail -F webrtc-client.log
3. cat > signal
4. ../demo/chat/chat
Look for the offer in terminal 2; copy and paste it into terminal 4.
Copy and paste the answer from terminal 4 to terminal 3. At this point
you should see some TLS garbage in the chat window.
More documentation on the way. More documentation on the way.

9
torrc
View file

@ -1,4 +1,5 @@
UseBridges 1 UseBridges 1
ClientTransportPlugin webrtc exec ./webrtc-client DataDirectory datadir
Bridge webrtc 0.0.3.0:1 ClientTransportPlugin webrtc exec ./webrtc-client
Bridge webrtc 0.0.3.0:1

View file

@ -1,176 +1,344 @@
package main package main
import ( import (
"fmt" "bufio"
"log" "fmt"
"net" "io"
"os" "log"
"os/signal" "net"
"syscall" "os"
"os/signal"
"github.com/keroserene/go-webrtc" "sync"
"github.com/keroserene/go-webrtc/data" "syscall"
"time"
"git.torproject.org/pluggable-transports/goptlib.git"
) "github.com/keroserene/go-webrtc"
"github.com/keroserene/go-webrtc/data"
var ptInfo pt.ClientInfo
var logFile *os.File "git.torproject.org/pluggable-transports/goptlib.git"
)
// When a connection handler starts, +1 is written to this channel; when it
// ends, -1 is written. var ptInfo pt.ClientInfo
var handlerChan = make(chan int) var logFile *os.File
func handler(conn *pt.SocksConn) error { var notImplemented = fmt.Errorf("not implemented")
handlerChan <- 1
defer func() { // When a connection handler starts, +1 is written to this channel; when it
handlerChan <- -1 // ends, -1 is written.
}() var handlerChan = make(chan int)
defer conn.Close()
var signalChan = make(chan *webrtc.SessionDescription)
config := webrtc.NewConfiguration(webrtc.OptionIceServer("stun:stun.l.google.com:19302"))
pc, err := webrtc.NewPeerConnection(config) func copyLoop(a, b net.Conn) {
if err != nil { var wg sync.WaitGroup
log.Printf("NewPeerConnection: %s", err) wg.Add(2)
return err
} go func() {
io.Copy(b, a)
// For now, the Go client is always the offerer. wg.Done()
// TODO: Copy paste signaling }()
go func() {
pc.OnNegotiationNeeded = func() { io.Copy(a, b)
// log.Println("OnNegotiationNeeded") wg.Done()
go func() { }()
offer, err := pc.CreateOffer()
if err != nil { wg.Wait()
log.Printf("CreateOffer: %s", err) }
return
} type webRTCConn struct {
fmt.Fprintln(logFile, offer.Serialize()) pc *webrtc.PeerConnection
pc.SetLocalDescription(offer) dc *data.Channel
}() recvPipe *io.PipeReader
} }
pc.OnIceCandidate = func(candidate webrtc.IceCandidate) {
// log.Printf("OnIceCandidate %q", candidate.Candidate) func (c *webRTCConn) Read(b []byte) (int, error) {
fmt.Fprintln(logFile, candidate.Serialize()) return c.recvPipe.Read(b)
} }
pc.OnDataChannel = func(channel *data.Channel) {
log.Println("OnDataChannel") func (c *webRTCConn) Write(b []byte) (int, error) {
panic("OnDataChannel") log.Printf("webrtc Write %d %q", len(b), string(b))
} err := c.dc.Send(b)
if err != nil {
dc, err := pc.CreateDataChannel("test", data.Init{}) return 0, err
if err != nil { }
log.Printf("CreateDataChannel: %s", err) return len(b), err
return err }
}
dc.OnOpen = func() { func (c *webRTCConn) Close() error {
log.Println("OnOpen channel") // Data channel closed implicitly?
} return c.pc.Close()
dc.OnClose = func() { }
log.Println("OnClose channel")
} func (c *webRTCConn) LocalAddr() net.Addr {
dc.OnMessage = func(msg []byte) { return nil
log.Println("OnMessage channel %d %q", len(msg), msg) }
}
func (c *webRTCConn) RemoteAddr() net.Addr {
// defer close channel? return nil
}
err = conn.Grant(&net.TCPAddr{IP: net.IPv4zero, Port: 0})
if err != nil { func (c *webRTCConn) SetDeadline(t time.Time) error {
return err return fmt.Errorf("SetDeadline not implemented")
} }
<-make(chan int) func (c *webRTCConn) SetReadDeadline(t time.Time) error {
return fmt.Errorf("SetReadDeadline not implemented")
return nil }
}
func (c *webRTCConn) SetWriteDeadline(t time.Time) error {
func acceptLoop(ln *pt.SocksListener) error { return fmt.Errorf("SetWriteDeadline not implemented")
defer ln.Close() }
for {
conn, err := ln.AcceptSocks() func dialWebRTC(config *webrtc.Configuration) (*webRTCConn, error) {
if err != nil { blobChan := make(chan string)
if e, ok := err.(net.Error); ok && e.Temporary() { errChan := make(chan error)
continue openChan := make(chan bool)
}
return err pc, err := webrtc.NewPeerConnection(config)
} if err != nil {
go handler(conn) log.Printf("NewPeerConnection: %s", err)
} return nil, err
} }
func main() { pc.OnNegotiationNeeded = func() {
var err error log.Println("OnNegotiationNeeded")
go func() {
logFile, err = os.OpenFile("webrtc-client.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) offer, err := pc.CreateOffer()
if err != nil { if err != nil {
log.Fatal(err) errChan <- err
} return
defer logFile.Close() }
log.SetOutput(logFile) err = pc.SetLocalDescription(offer)
if err != nil {
log.Println("starting") errChan <- err
return
webrtc.SetLoggingVerbosity(1) }
}()
ptInfo, err = pt.ClientSetup(nil) }
if err != nil { pc.OnIceCandidate = func(candidate webrtc.IceCandidate) {
os.Exit(1) log.Printf("OnIceCandidate %s", candidate.Serialize())
} // Allow candidates to accumulate until OnIceComplete.
}
if ptInfo.ProxyURL != nil { pc.OnIceComplete = func() {
pt.ProxyError("proxy is not supported") log.Printf("OnIceComplete")
os.Exit(1) blobChan <- pc.LocalDescription().Serialize()
} }
pc.OnDataChannel = func(channel *data.Channel) {
listeners := make([]net.Listener, 0) log.Println("OnDataChannel")
for _, methodName := range ptInfo.MethodNames { panic("OnDataChannel")
switch methodName { }
case "webrtc":
ln, err := pt.ListenSocks("tcp", "127.0.0.1:0") pr, pw := io.Pipe()
if err != nil {
pt.CmethodError(methodName, err.Error()) dc, err := pc.CreateDataChannel("test", data.Init{})
break if err != nil {
} log.Printf("CreateDataChannel: %s", err)
go acceptLoop(ln) return nil, err
pt.Cmethod(methodName, ln.Version(), ln.Addr()) }
listeners = append(listeners, ln) dc.OnOpen = func() {
default: log.Println("OnOpen channel")
pt.CmethodError(methodName, "no such method") openChan <- true
} }
} dc.OnClose = func() {
pt.CmethodsDone() log.Println("OnClose channel")
pw.Close()
var numHandlers int = 0 close(openChan)
var sig os.Signal }
sigChan := make(chan os.Signal, 1) dc.OnMessage = func(msg []byte) {
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) log.Printf("OnMessage channel %d %q", len(msg), msg)
n, err := pw.Write(msg)
// wait for first signal if err != nil {
sig = nil pw.CloseWithError(err)
for sig == nil { }
select { if n != len(msg) {
case n := <-handlerChan: panic("short write")
numHandlers += n }
case sig = <-sigChan: }
}
} select {
for _, ln := range listeners { case err := <-errChan:
ln.Close() pc.Close()
} return nil, err
case offer := <-blobChan:
if sig == syscall.SIGTERM { log.Printf("----------------")
return fmt.Fprintln(logFile, offer)
} log.Printf("----------------")
}
// wait for second signal or no more handlers
sig = nil log.Printf("waiting for answer")
for sig == nil && numHandlers != 0 { answer, ok := <-signalChan
select {
case n := <-handlerChan: if !ok {
numHandlers += n pc.Close()
case sig = <-sigChan: return nil, fmt.Errorf("no answer received")
} }
} log.Printf("got answer %s", answer.Serialize())
} err = pc.SetRemoteDescription(answer)
if err != nil {
pc.Close()
return nil, err
}
// Wait until data channel is open; otherwise for example sends may get
// lost.
_, ok = <-openChan
if !ok {
pc.Close()
return nil, fmt.Errorf("failed to open data channel")
}
return &webRTCConn{pc: pc, dc: dc, recvPipe: pr}, nil
}
func handler(conn *pt.SocksConn) error {
handlerChan <- 1
defer func() {
handlerChan <- -1
}()
defer conn.Close()
config := webrtc.NewConfiguration(webrtc.OptionIceServer("stun:stun.l.google.com:19302"))
remote, err := dialWebRTC(config)
if err != nil {
conn.Reject()
return err
}
defer remote.Close()
err = conn.Grant(&net.TCPAddr{IP: net.IPv4zero, Port: 0})
if err != nil {
return err
}
copyLoop(conn, remote)
return nil
}
func acceptLoop(ln *pt.SocksListener) error {
defer ln.Close()
for {
conn, err := ln.AcceptSocks()
if err != nil {
if e, ok := err.(net.Error); ok && e.Temporary() {
continue
}
return err
}
go func() {
err := handler(conn)
if err != nil {
log.Printf("handler error: %s", err)
}
}()
}
}
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
}
signalChan <- sdp
}
log.Printf("close signalChan")
close(signalChan)
if err := s.Err(); err != nil {
log.Printf("signal FIFO: %s", err)
}
}
func main() {
var err error
logFile, err = os.OpenFile("webrtc-client.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
log.Fatal(err)
}
defer logFile.Close()
log.SetOutput(logFile)
log.Println("starting")
// This FIFO receives signaling messages.
err = syscall.Mkfifo("signal", 0600)
if err != nil {
if err.(syscall.Errno) != syscall.EEXIST {
log.Fatal(err)
}
}
signalFile, err := os.OpenFile("signal", os.O_RDONLY, 0600)
if err != nil {
log.Fatal(err)
}
defer signalFile.Close()
go readSignalingMessages(signalFile)
webrtc.SetLoggingVerbosity(1)
ptInfo, err = pt.ClientSetup(nil)
if err != nil {
os.Exit(1)
}
if ptInfo.ProxyURL != nil {
pt.ProxyError("proxy is not supported")
os.Exit(1)
}
listeners := make([]net.Listener, 0)
for _, methodName := range ptInfo.MethodNames {
switch methodName {
case "webrtc":
ln, err := pt.ListenSocks("tcp", "127.0.0.1:0")
if err != nil {
pt.CmethodError(methodName, err.Error())
break
}
go acceptLoop(ln)
pt.Cmethod(methodName, ln.Version(), ln.Addr())
listeners = append(listeners, ln)
default:
pt.CmethodError(methodName, "no such method")
}
}
pt.CmethodsDone()
var numHandlers int = 0
var sig os.Signal
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
// wait for first signal
sig = nil
for sig == nil {
select {
case n := <-handlerChan:
numHandlers += n
case sig = <-sigChan:
}
}
for _, ln := range listeners {
ln.Close()
}
if sig == syscall.SIGTERM {
return
}
// wait for second signal or no more handlers
sig = nil
for sig == nil && numHandlers != 0 {
select {
case n := <-handlerChan:
numHandlers += n
case sig = <-sigChan:
}
}
}