Skip to content
Snippets Groups Projects
snowflake.go 5.48 KiB
Newer Older
  • Learn to ignore specific revisions
  • // Client transport plugin for the Snowflake pluggable transport.
    
    	"io/ioutil"
    
    	"path/filepath"
    
    	pt "git.torproject.org/pluggable-transports/goptlib.git"
    
    	sf "git.torproject.org/pluggable-transports/snowflake.git/client/lib"
    
    	"git.torproject.org/pluggable-transports/snowflake.git/common/safelog"
    
    	"github.com/pion/webrtc"
    
    	DefaultSnowflakeCapacity = 1
    
    // Maintain |SnowflakeCapacity| number of available WebRTC connections, to
    // transfer to the Tor SOCKS handler when needed.
    
    func ConnectLoop(snowflakes sf.SnowflakeCollector) {
    
    		// Check if ending is necessary.
    
    		_, err := snowflakes.Collect()
    
    David Fifield's avatar
    David Fifield committed
    		if err != nil {
    
    			log.Printf("WebRTC: %v  Retrying in %v...",
    
    David Fifield's avatar
    David Fifield committed
    				err, sf.ReconnectTimeout)
    
    		case <-time.After(sf.ReconnectTimeout):
    
    		case <-snowflakes.Melted():
    			log.Println("ConnectLoop: stopped.")
    			return
    
    // Accept local SOCKS connections and pass them to the handler.
    
    func socksAcceptLoop(ln *pt.SocksListener, snowflakes sf.SnowflakeCollector) {
    
    	defer ln.Close()
    	for {
    		conn, err := ln.AcceptSocks()
    		if err != nil {
    
    David Fifield's avatar
    David Fifield committed
    			if err, ok := err.(net.Error); ok && err.Temporary() {
    
    			log.Printf("SOCKS accept error: %s", err)
    			break
    
    		log.Printf("SOCKS accepted: %v", conn.Req)
    
    		go func() {
    			defer conn.Close()
    			err = sf.Handler(conn, snowflakes)
    			if err != nil {
    				log.Printf("handler error: %s", err)
    			}
    		}()
    
    David Fifield's avatar
    David Fifield committed
    // s is a comma-separated list of ICE server URLs.
    
    func parseIceServers(s string) []webrtc.ICEServer {
    	var servers []webrtc.ICEServer
    	s = strings.TrimSpace(s)
    	if len(s) == 0 {
    		return nil
    	}
    	urls := strings.Split(s, ",")
    	for _, url := range urls {
    
    		url = strings.TrimSpace(url)
    
    		servers = append(servers, webrtc.ICEServer{
    			URLs: []string{url},
    		})
    	}
    	return servers
    }
    
    
    	iceServersCommas := flag.String("ice", "", "comma-separated list of ICE servers")
    
    	brokerURL := flag.String("url", "", "URL of signaling broker")
    	frontDomain := flag.String("front", "", "front domain")
    
    	logFilename := flag.String("log", "", "name of log file")
    
    	logToStateDir := flag.Bool("logToStateDir", false, "resolve the log file relative to tor's pt state dir")
    
    	max := flag.Int("max", DefaultSnowflakeCapacity,
    		"capacity for number of multiplexed WebRTC peers")
    
    	log.SetFlags(log.LstdFlags | log.LUTC)
    
    David Fifield's avatar
    David Fifield committed
    	// Don't write to stderr; versions of tor earlier than about 0.3.5.6 do
    	// not read from the pipe, and eventually we will deadlock because the
    	// buffer is full.
    
    	// https://bugs.torproject.org/26360
    	// https://bugs.torproject.org/25600#comment:14
    
    	var logOutput = ioutil.Discard
    
    		if *logToStateDir {
    			stateDir, err := pt.MakeStateDir()
    			if err != nil {
    				log.Fatal(err)
    			}
    			*logFilename = filepath.Join(stateDir, *logFilename)
    		}
    
    		logFile, err := os.OpenFile(*logFilename,
    			os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
    		if err != nil {
    			log.Fatal(err)
    		}
    		defer logFile.Close()
    
    		logOutput = logFile
    
    David Fifield's avatar
    David Fifield committed
    	// We want to send the log output through our scrubber first
    
    	log.SetOutput(&safelog.LogScrubber{Output: logOutput})
    
    
    	log.Println("\n\n\n --- Starting Snowflake Client ---")
    
    
    	iceServers := parseIceServers(*iceServersCommas)
    
    	log.Printf("Using ICE servers:")
    	for _, server := range iceServers {
    		log.Printf("url: %v", strings.Join(server.URLs, " "))
    	}
    
    	// Prepare to collect remote WebRTC peers.
    
    	snowflakes := sf.NewPeers(*max)
    
    
    	// Use potentially domain-fronting broker to rendezvous.
    
    	broker, err := sf.NewBrokerChannel(*brokerURL, *frontDomain, sf.CreateBrokerTransport())
    	if err != nil {
    		log.Fatalf("parsing broker URL: %v", err)
    	}
    
    	snowflakes.Tongue = sf.NewWebRTCDialer(broker, iceServers)
    
    
    	// Use a real logger to periodically output how much traffic is happening.
    
    	snowflakes.BytesLogger = &sf.BytesSyncLogger{
    		InboundChan:  make(chan int, 5),
    		OutboundChan: make(chan int, 5),
    		Inbound:      0,
    		Outbound:     0,
    		InEvents:     0,
    		OutEvents:    0,
    
    	go ConnectLoop(snowflakes)
    
    	// Begin goptlib client process.
    	ptInfo, err := pt.ClientSetup(nil)
    
    David Fifield's avatar
    David Fifield committed
    		log.Fatal(err)
    
    		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())
    
    			log.Printf("Started SOCKS listener at %v.", ln.Addr())
    
    			pt.Cmethod(methodName, ln.Version(), ln.Addr())
    			listeners = append(listeners, ln)
    		default:
    
    			pt.CmethodError(methodName, "no such method")
    
    		}
    	}
    	pt.CmethodsDone()
    
    	sigChan := make(chan os.Signal, 1)
    
    	signal.Notify(sigChan, syscall.SIGTERM)
    
    	if os.Getenv("TOR_PT_EXIT_ON_STDIN_CLOSE") == "1" {
    		// This environment variable means we should treat EOF on stdin
    		// just like SIGTERM: https://bugs.torproject.org/15435.
    		go func() {
    
    			if _, err := io.Copy(ioutil.Discard, os.Stdin); err != nil {
    				log.Printf("calling io.Copy(ioutil.Discard, os.Stdin) returned error: %v", err)
    			}
    
    			log.Printf("synthesizing SIGTERM because of stdin close")
    			sigChan <- syscall.SIGTERM
    		}()
    	}
    
    
    David Fifield's avatar
    David Fifield committed
    	// Wait for a signal.
    
    David Fifield's avatar
    David Fifield committed
    	// Signal received, shut down.
    
    	for _, ln := range listeners {
    		ln.Close()
    	}
    
    	log.Println("snowflake is done.")