Permalink
Cannot retrieve contributors at this time
226 lines (208 sloc)
5.54 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
| // Client transport plugin for the Snowflake pluggable transport. | |
| package main | |
| import ( | |
| "errors" | |
| "flag" | |
| "io" | |
| "log" | |
| "net" | |
| "os" | |
| "os/signal" | |
| "path" | |
| "sync" | |
| "syscall" | |
| "time" | |
| "git.torproject.org/pluggable-transports/goptlib.git" | |
| "github.com/keroserene/go-webrtc" | |
| ) | |
| const ( | |
| ReconnectTimeout = 10 | |
| DefaultSnowflakeCapacity = 1 | |
| SnowflakeTimeout = 30 | |
| ) | |
| // When a connection handler starts, +1 is written to this channel; when it | |
| // ends, -1 is written. | |
| var handlerChan = make(chan int) | |
| // Maintain |SnowflakeCapacity| number of available WebRTC connections, to | |
| // transfer to the Tor SOCKS handler when needed. | |
| func ConnectLoop(snowflakes SnowflakeCollector) { | |
| for { | |
| // Check if ending is necessary. | |
| _, err := snowflakes.Collect() | |
| if nil != err { | |
| log.Println("WebRTC:", err, | |
| " Retrying in", ReconnectTimeout, "seconds...") | |
| } | |
| select { | |
| case <-time.After(time.Second * ReconnectTimeout): | |
| continue | |
| case <-snowflakes.Melted(): | |
| log.Println("ConnectLoop: stopped.") | |
| return | |
| } | |
| } | |
| } | |
| // Accept local SOCKS connections and pass them to the handler. | |
| func socksAcceptLoop(ln *pt.SocksListener, snowflakes SnowflakeCollector) error { | |
| defer ln.Close() | |
| log.Println("Started SOCKS listener.") | |
| for { | |
| log.Println("SOCKS listening...") | |
| conn, err := ln.AcceptSocks() | |
| log.Println("SOCKS accepted: ", conn.Req) | |
| if err != nil { | |
| if e, ok := err.(net.Error); ok && e.Temporary() { | |
| continue | |
| } | |
| return err | |
| } | |
| err = handler(conn, snowflakes) | |
| if err != nil { | |
| log.Printf("handler error: %s", err) | |
| } | |
| } | |
| } | |
| // Given an accepted SOCKS connection, establish a WebRTC connection to the | |
| // remote peer and exchange traffic. | |
| func handler(socks SocksConnector, snowflakes SnowflakeCollector) error { | |
| handlerChan <- 1 | |
| defer func() { | |
| handlerChan <- -1 | |
| }() | |
| // Obtain an available WebRTC remote. May block. | |
| snowflake := snowflakes.Pop() | |
| if nil == snowflake { | |
| socks.Reject() | |
| return errors.New("handler: Received invalid Snowflake") | |
| } | |
| defer socks.Close() | |
| defer snowflake.Close() | |
| log.Println("---- Handler: snowflake assigned ----") | |
| err := socks.Grant(&net.TCPAddr{IP: net.IPv4zero, Port: 0}) | |
| if err != nil { | |
| return err | |
| } | |
| go func() { | |
| // When WebRTC resets, close the SOCKS connection too. | |
| snowflake.WaitForReset() | |
| socks.Close() | |
| }() | |
| // Begin exchanging data. Either WebRTC or localhost SOCKS will close first. | |
| // In eithercase, this closes the handler and induces a new handler. | |
| copyLoop(socks, snowflake) | |
| log.Println("---- Handler: closed ---") | |
| return nil | |
| } | |
| // Exchanges bytes between two ReadWriters. | |
| // (In this case, between a SOCKS and WebRTC connection.) | |
| func copyLoop(a, b io.ReadWriter) { | |
| var wg sync.WaitGroup | |
| wg.Add(2) | |
| go func() { | |
| io.Copy(b, a) | |
| wg.Done() | |
| }() | |
| go func() { | |
| io.Copy(a, b) | |
| wg.Done() | |
| }() | |
| wg.Wait() | |
| log.Println("copy loop ended") | |
| } | |
| func main() { | |
| webrtc.SetLoggingVerbosity(1) | |
| stateDir, err := pt.MakeStateDir() | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| logFile, err := os.OpenFile(path.Join(stateDir, "snowflake.log"), | |
| os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| defer logFile.Close() | |
| log.SetOutput(logFile) | |
| var iceServers IceServerList | |
| log.Println("\n\n\n --- Starting Snowflake Client ---") | |
| flag.Var(&iceServers, "ice", "comma-separated list of ICE servers") | |
| brokerURL := flag.String("url", "", "URL of signaling broker") | |
| frontDomain := flag.String("front", "", "front domain") | |
| max := flag.Int("max", DefaultSnowflakeCapacity, | |
| "capacity for number of multiplexed WebRTC peers") | |
| flag.Parse() | |
| // Prepare to collect remote WebRTC peers. | |
| snowflakes := NewPeers(*max) | |
| if "" != *brokerURL { | |
| // Use potentially domain-fronting broker to rendezvous. | |
| broker := NewBrokerChannel(*brokerURL, *frontDomain, CreateBrokerTransport()) | |
| snowflakes.Tongue = NewWebRTCDialer(broker, iceServers) | |
| } else { | |
| // Otherwise, use manual copy and pasting of SDP messages. | |
| snowflakes.Tongue = NewCopyPasteDialer(iceServers) | |
| } | |
| if nil == snowflakes.Tongue { | |
| log.Fatal("Unable to prepare rendezvous method.") | |
| return | |
| } | |
| // Use a real logger to periodically output how much traffic is happening. | |
| snowflakes.BytesLogger = &BytesSyncLogger{ | |
| inboundChan: make(chan int, 5), outboundChan: make(chan int, 5), | |
| inbound: 0, outbound: 0, inEvents: 0, outEvents: 0, | |
| } | |
| go snowflakes.BytesLogger.Log() | |
| go ConnectLoop(snowflakes) | |
| // Begin goptlib client process. | |
| ptInfo, err := pt.ClientSetup(nil) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| 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 "snowflake": | |
| // TODO: Be able to recover when SOCKS dies. | |
| ln, err := pt.ListenSocks("tcp", "127.0.0.1:0") | |
| if err != nil { | |
| pt.CmethodError(methodName, err.Error()) | |
| break | |
| } | |
| go socksAcceptLoop(ln, snowflakes) | |
| 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() | |
| } | |
| snowflakes.End() | |
| // wait for second signal or no more handlers | |
| sig = nil | |
| for sig == nil && numHandlers != 0 { | |
| select { | |
| case n := <-handlerChan: | |
| numHandlers += n | |
| case sig = <-sigChan: | |
| } | |
| } | |
| log.Println("snowflake is done.") | |
| } |