snowflake.go 5.5 KB
Newer Older
1
// Client transport plugin for the Snowflake pluggable transport.
2
3
4
package main

import (
5
	"flag"
6
	"io"
7
	"io/ioutil"
8
9
10
11
	"log"
	"net"
	"os"
	"os/signal"
12
	"path/filepath"
13
	"strings"
14
	"syscall"
15
	"time"
16

17
	"git.torproject.org/pluggable-transports/goptlib.git"
18
	sf "git.torproject.org/pluggable-transports/snowflake.git/client/lib"
19
	"git.torproject.org/pluggable-transports/snowflake.git/common/safelog"
20
	"github.com/pion/webrtc"
21
22
)

23
const (
24
	DefaultSnowflakeCapacity = 1
25
26
)

27
28
// Maintain |SnowflakeCapacity| number of available WebRTC connections, to
// transfer to the Tor SOCKS handler when needed.
29
func ConnectLoop(snowflakes sf.SnowflakeCollector) {
30
	for {
31
		// Check if ending is necessary.
32
		_, err := snowflakes.Collect()
33
		if nil != err {
34
			log.Println("WebRTC:", err,
35
				" Retrying in", sf.ReconnectTimeout, "seconds...")
36
37
		}
		select {
38
		case <-time.After(time.Second * sf.ReconnectTimeout):
39
			continue
40
41
42
		case <-snowflakes.Melted():
			log.Println("ConnectLoop: stopped.")
			return
43
44
45
46
		}
	}
}

47
// Accept local SOCKS connections and pass them to the handler.
48
func socksAcceptLoop(ln *pt.SocksListener, snowflakes sf.SnowflakeCollector) error {
49
	defer ln.Close()
50
	log.Println("Started SOCKS listener.")
51
	for {
52
		log.Println("SOCKS listening...")
53
54
55
56
57
58
59
		conn, err := ln.AcceptSocks()
		if err != nil {
			if e, ok := err.(net.Error); ok && e.Temporary() {
				continue
			}
			return err
		}
60
		log.Println("SOCKS accepted: ", conn.Req)
61
		err = sf.Handler(conn, snowflakes)
62
63
64
		if err != nil {
			log.Printf("handler error: %s", err)
		}
65
	}
66
67
}

68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
//s is a comma-separated list of ICE server URLs
func parseIceServers(s string) []webrtc.ICEServer {
	var servers []webrtc.ICEServer
	log.Println(s)
	s = strings.TrimSpace(s)
	if len(s) == 0 {
		return nil
	}
	urls := strings.Split(s, ",")
	log.Printf("Using ICE Servers:")
	for _, url := range urls {
		log.Printf("url: %s", url)
		servers = append(servers, webrtc.ICEServer{
			URLs: []string{url},
		})
	}
	return servers
}

87
func main() {
88
	iceServersCommas := flag.String("ice", "", "comma-separated list of ICE servers")
89
90
	brokerURL := flag.String("url", "", "URL of signaling broker")
	frontDomain := flag.String("front", "", "front domain")
91
	logFilename := flag.String("log", "", "name of log file")
92
	logToStateDir := flag.Bool("logToStateDir", false, "resolve the log file relative to tor's pt state dir")
93
94
	max := flag.Int("max", DefaultSnowflakeCapacity,
		"capacity for number of multiplexed WebRTC peers")
95
96
	flag.Parse()

97
	log.SetFlags(log.LstdFlags | log.LUTC)
98

99
100
101
102
103
104
	// 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 io.Writer = ioutil.Discard
105
	if *logFilename != "" {
106
107
108
109
110
111
112
		if *logToStateDir {
			stateDir, err := pt.MakeStateDir()
			if err != nil {
				log.Fatal(err)
			}
			*logFilename = filepath.Join(stateDir, *logFilename)
		}
113
114
115
116
117
118
		logFile, err := os.OpenFile(*logFilename,
			os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
		if err != nil {
			log.Fatal(err)
		}
		defer logFile.Close()
119
		logOutput = logFile
120
	}
121
122
	//We want to send the log output through our scrubber first
	log.SetOutput(&safelog.LogScrubber{Output: logOutput})
123
124
125

	log.Println("\n\n\n --- Starting Snowflake Client ---")

126
	iceServers := parseIceServers(*iceServersCommas)
127

128
	// Prepare to collect remote WebRTC peers.
129
	snowflakes := sf.NewPeers(*max)
Arlo Breault's avatar
Arlo Breault committed
130
131
132
133
134

	// Use potentially domain-fronting broker to rendezvous.
	broker := sf.NewBrokerChannel(*brokerURL, *frontDomain, sf.CreateBrokerTransport())
	snowflakes.Tongue = sf.NewWebRTCDialer(broker, iceServers)

135
136
137
138
	if nil == snowflakes.Tongue {
		log.Fatal("Unable to prepare rendezvous method.")
		return
	}
139
	// Use a real logger to periodically output how much traffic is happening.
140
141
142
143
144
145
146
	snowflakes.BytesLogger = &sf.BytesSyncLogger{
		InboundChan:  make(chan int, 5),
		OutboundChan: make(chan int, 5),
		Inbound:      0,
		Outbound:     0,
		InEvents:     0,
		OutEvents:    0,
147
	}
148
	go snowflakes.BytesLogger.Log()
149

150
	go ConnectLoop(snowflakes)
151

152
153
	// Begin goptlib client process.
	ptInfo, err := pt.ClientSetup(nil)
154
	if err != nil {
David Fifield's avatar
David Fifield committed
155
		log.Fatal(err)
156
157
158
159
160
161
162
163
	}
	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 {
Arlo Breault's avatar
Arlo Breault committed
164
		case "snowflake":
165
			// TODO: Be able to recover when SOCKS dies.
166
167
168
169
170
			ln, err := pt.ListenSocks("tcp", "127.0.0.1:0")
			if err != nil {
				pt.CmethodError(methodName, err.Error())
				break
			}
171
			go socksAcceptLoop(ln, snowflakes)
172
173
174
175
176
177
178
179
180
181
182
			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)
183
	signal.Notify(sigChan, syscall.SIGTERM)
184

185
186
187
188
189
190
191
192
193
194
	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() {
			io.Copy(ioutil.Discard, os.Stdin)
			log.Printf("synthesizing SIGTERM because of stdin close")
			sigChan <- syscall.SIGTERM
		}()
	}

195
	// keep track of handlers and wait for a signal
196
197
198
	sig = nil
	for sig == nil {
		select {
199
		case n := <-sf.HandlerChan:
200
201
202
203
			numHandlers += n
		case sig = <-sigChan:
		}
	}
204
205

	// signal received, shut down
206
207
208
	for _, ln := range listeners {
		ln.Close()
	}
209
	snowflakes.End()
210
	for numHandlers > 0 {
211
		numHandlers += <-sf.HandlerChan
212
	}
213
	log.Println("snowflake is done.")
214
}