diff --git a/client/meek-webrtc.go b/client/meek-webrtc.go new file mode 100644 index 0000000..6a50bd1 --- /dev/null +++ b/client/meek-webrtc.go @@ -0,0 +1,84 @@ +// Exchange WebRTC SessionDescriptions over meek. +// Much of this source is extracted from meek-client.go. +package main + +import ( + "bytes" + "io/ioutil" + "net/http" + "net/url" + + "github.com/keroserene/go-webrtc" +) + +// RequestInfo encapsulates all the configuration used for a request–response +// roundtrip, including variables that may come from SOCKS args or from the +// command line. +type RequestInfo struct { + // What to put in the X-Session-ID header. + // SessionID string + // The URL to request. + URL *url.URL + // The Host header to put in the HTTP request (optional and may be + // different from the host name in URL). + Host string +} + +func NewRequestInfo(meekUrl string, front string) *RequestInfo { + info := new(RequestInfo) + requestUrl, err := url.Parse(meekUrl) + if nil != err { + return nil + } + info.URL = requestUrl + info.Host = info.URL.Host + info.URL.Host = front + return info +} + +// Meek Signalling Channel +type MeekChannel struct { + info *RequestInfo + // Used to make all requests. + transport http.Transport +} + +func NewMeekChannel(info *RequestInfo) *MeekChannel { + m := new(MeekChannel) + // We make a copy of DefaultTransport because we want the default Dial + // and TLSHandshakeTimeout settings. But we want to disable the default + // ProxyFromEnvironment setting. Proxy is overridden below if + // options.ProxyURL is set. + m.transport = *http.DefaultTransport.(*http.Transport) + m.transport.Proxy = nil + m.info = info + return m +} + +// Do an HTTP roundtrip using the payload data in buf. +func (m *MeekChannel) roundTripHTTP(buf []byte) (*http.Response, error) { + req, err := http.NewRequest("POST", m.info.URL.String(), bytes.NewReader(buf)) + if nil != err { + return nil, err + } + if "" != m.info.Host { + req.Host = m.info.Host + } + // req.Header.Set("X-Session-Id", m.info.SessionID) + return m.transport.RoundTrip(req) +} + +// Send an SDP offer to the meek facilitator, and wait for an SDP answer from +// the assigned proxy in the response. +func (m *MeekChannel) Negotiate(offer *webrtc.SessionDescription) ( + *webrtc.SessionDescription, error) { + buf := []byte(offer.Serialize()) + resp, err := m.roundTripHTTP(buf) + if nil != err { + return nil, err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + answer := webrtc.DeserializeSessionDescription(string(body)) + return answer, nil +} diff --git a/client/snowflake.go b/client/snowflake.go index 9654cae..63e3059 100644 --- a/client/snowflake.go +++ b/client/snowflake.go @@ -1,3 +1,6 @@ +// Client transport plugin for the snowflake pluggable transport. +// +// TODO: Use meek for signalling. package main import ( @@ -18,6 +21,13 @@ import ( "git.torproject.org/pluggable-transports/goptlib.git" ) +// Hard-coded meek signalling channel for now. +// TODO: expose as param +const ( + MEEK_URL = "not implemented yet" + FRONT_DOMAIN = "www.google.com" +) + var ptInfo pt.ClientInfo var logFile *os.File @@ -89,8 +99,10 @@ func (c *webRTCConn) SetWriteDeadline(t time.Time) error { return fmt.Errorf("SetWriteDeadline not implemented") } -func dialWebRTC(config *webrtc.Configuration) (*webRTCConn, error) { - blobChan := make(chan string) +func dialWebRTC(config *webrtc.Configuration, meek *MeekChannel) ( + *webRTCConn, error) { + + offerChan := make(chan *webrtc.SessionDescription) errChan := make(chan error) openChan := make(chan bool) @@ -123,7 +135,7 @@ func dialWebRTC(config *webrtc.Configuration) (*webRTCConn, error) { // TODO: This may soon be deprecated, consider OnIceGatheringStateChange. pc.OnIceComplete = func() { log.Printf("OnIceComplete") - blobChan <- pc.LocalDescription().Serialize() + offerChan <- pc.LocalDescription() } pc.OnDataChannel = func(channel *data.Channel) { log.Println("OnDataChannel") @@ -162,10 +174,22 @@ func dialWebRTC(config *webrtc.Configuration) (*webRTCConn, error) { case err := <-errChan: pc.Close() return nil, err - case offer := <-blobChan: + case offer := <-offerChan: log.Printf("----------------") - fmt.Fprintln(logFile, "\n"+offer+"\n") + fmt.Fprintln(logFile, "\n"+offer.Serialize()+"\n") log.Printf("----------------") + go func() { + log.Printf("Sending offer via meek...") + answer, err := meek.Negotiate(pc.LocalDescription()) + if nil != err { + log.Printf("Signalling error: %s", err) + } + if nil == answer { + log.Printf("No answer received from meek channel.") + } else { + signalChan <- answer + } + }() } log.Printf("waiting for answer") @@ -184,6 +208,7 @@ func dialWebRTC(config *webrtc.Configuration) (*webRTCConn, error) { // Wait until data channel is open; otherwise for example sends may get // lost. + // TODO: Buffering *should* work though. _, ok = <-openChan if !ok { pc.Close() @@ -215,8 +240,16 @@ func handler(conn *pt.SocksConn) error { }() defer conn.Close() - config := webrtc.NewConfiguration(webrtc.OptionIceServer("stun:stun.l.google.com:19302")) - remote, err := dialWebRTC(config) + // go func() { + // }() + // Prepare meek signalling channel. + info := NewRequestInfo(MEEK_URL, FRONT_DOMAIN) + meek := NewMeekChannel(info) + + config := webrtc.NewConfiguration( + webrtc.OptionIceServer("stun:stun.l.google.com:19302")) + // remote, err := dialWebRTC(config, nil) + remote, err := dialWebRTC(config, meek) if err != nil { conn.Reject() return err