snowflake.go 6.01 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
	pt "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/v2"
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()
David Fifield's avatar
David Fifield committed
33
		if err != nil {
34
			log.Printf("WebRTC: %v  Retrying in %v...",
David Fifield's avatar
David Fifield committed
35
				err, sf.ReconnectTimeout)
36
37
		}
		select {
38
		case <-time.After(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) {
49
50
51
52
	defer ln.Close()
	for {
		conn, err := ln.AcceptSocks()
		if err != nil {
David Fifield's avatar
David Fifield committed
53
			if err, ok := err.(net.Error); ok && err.Temporary() {
54
55
				continue
			}
56
57
			log.Printf("SOCKS accept error: %s", err)
			break
58
		}
59
		log.Printf("SOCKS accepted: %v", conn.Req)
60
61
62
63
64
65
66
		go func() {
			defer conn.Close()
			err = sf.Handler(conn, snowflakes)
			if err != nil {
				log.Printf("handler error: %s", err)
			}
		}()
67
	}
68
69
}

David Fifield's avatar
David Fifield committed
70
// s is a comma-separated list of ICE server URLs.
71
72
73
74
75
76
77
78
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 {
79
		url = strings.TrimSpace(url)
80
81
82
83
84
85
86
		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
93
	logToStateDir := flag.Bool("log-to-state-dir", false, "resolve the log file relative to tor's pt state dir")
	keepLocalAddresses := flag.Bool("keep-local-addresses", false, "keep local LAN address ICE candidates")
Arlo Breault's avatar
Arlo Breault committed
94
	unsafeLogging := flag.Bool("unsafe-logging", false, "prevent logs from being scrubbed")
95
96
	max := flag.Int("max", DefaultSnowflakeCapacity,
		"capacity for number of multiplexed WebRTC peers")
97
98
99
100
101

	// Deprecated
	oldLogToStateDir := flag.Bool("logToStateDir", false, "use -log-to-state-dir instead")
	oldKeepLocalAddresses := flag.Bool("keepLocalAddresses", false, "use -keep-local-addresses instead")

102
103
	flag.Parse()

104
	log.SetFlags(log.LstdFlags | log.LUTC)
105

David Fifield's avatar
David Fifield committed
106
107
108
	// 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.
109
110
	// https://bugs.torproject.org/26360
	// https://bugs.torproject.org/25600#comment:14
111
	var logOutput = ioutil.Discard
112
	if *logFilename != "" {
113
		if *logToStateDir || *oldLogToStateDir {
114
115
116
117
118
119
			stateDir, err := pt.MakeStateDir()
			if err != nil {
				log.Fatal(err)
			}
			*logFilename = filepath.Join(stateDir, *logFilename)
		}
120
121
122
123
124
125
		logFile, err := os.OpenFile(*logFilename,
			os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
		if err != nil {
			log.Fatal(err)
		}
		defer logFile.Close()
126
		logOutput = logFile
127
	}
Arlo Breault's avatar
Arlo Breault committed
128
129
130
131
132
133
	if *unsafeLogging {
		log.SetOutput(logOutput)
	} else {
		// We want to send the log output through our scrubber first
		log.SetOutput(&safelog.LogScrubber{Output: logOutput})
	}
134
135
136

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

137
	iceServers := parseIceServers(*iceServersCommas)
138
139
140
141
	log.Printf("Using ICE servers:")
	for _, server := range iceServers {
		log.Printf("url: %v", strings.Join(server.URLs, " "))
	}
142

143
	// Prepare to collect remote WebRTC peers.
144
	snowflakes := sf.NewPeers(*max)
Arlo Breault's avatar
Arlo Breault committed
145
146

	// Use potentially domain-fronting broker to rendezvous.
147
148
149
	broker, err := sf.NewBrokerChannel(
		*brokerURL, *frontDomain, sf.CreateBrokerTransport(),
		*keepLocalAddresses || *oldKeepLocalAddresses)
150
151
152
	if err != nil {
		log.Fatalf("parsing broker URL: %v", err)
	}
Arlo Breault's avatar
Arlo Breault committed
153
154
	snowflakes.Tongue = sf.NewWebRTCDialer(broker, iceServers)

155
	// Use a real logger to periodically output how much traffic is happening.
156
157
158
159
160
161
162
	snowflakes.BytesLogger = &sf.BytesSyncLogger{
		InboundChan:  make(chan int, 5),
		OutboundChan: make(chan int, 5),
		Inbound:      0,
		Outbound:     0,
		InEvents:     0,
		OutEvents:    0,
163
	}
164
	go snowflakes.BytesLogger.Log()
165

166
	go ConnectLoop(snowflakes)
167

168
169
	// Begin goptlib client process.
	ptInfo, err := pt.ClientSetup(nil)
170
	if err != nil {
David Fifield's avatar
David Fifield committed
171
		log.Fatal(err)
172
173
	}
	if ptInfo.ProxyURL != nil {
174
		pt.ProxyError("proxy is not supported")
175
176
177
178
179
		os.Exit(1)
	}
	listeners := make([]net.Listener, 0)
	for _, methodName := range ptInfo.MethodNames {
		switch methodName {
Arlo Breault's avatar
Arlo Breault committed
180
		case "snowflake":
181
			// TODO: Be able to recover when SOCKS dies.
182
183
			ln, err := pt.ListenSocks("tcp", "127.0.0.1:0")
			if err != nil {
184
				pt.CmethodError(methodName, err.Error())
185
186
				break
			}
187
			log.Printf("Started SOCKS listener at %v.", ln.Addr())
188
			go socksAcceptLoop(ln, snowflakes)
189
190
191
			pt.Cmethod(methodName, ln.Version(), ln.Addr())
			listeners = append(listeners, ln)
		default:
192
			pt.CmethodError(methodName, "no such method")
193
194
195
196
197
		}
	}
	pt.CmethodsDone()

	sigChan := make(chan os.Signal, 1)
198
	signal.Notify(sigChan, syscall.SIGTERM)
199

200
201
202
203
	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() {
204
205
206
			if _, err := io.Copy(ioutil.Discard, os.Stdin); err != nil {
				log.Printf("calling io.Copy(ioutil.Discard, os.Stdin) returned error: %v", err)
			}
207
208
209
210
211
			log.Printf("synthesizing SIGTERM because of stdin close")
			sigChan <- syscall.SIGTERM
		}()
	}

David Fifield's avatar
David Fifield committed
212
	// Wait for a signal.
213
	<-sigChan
214

David Fifield's avatar
David Fifield committed
215
	// Signal received, shut down.
216
217
218
	for _, ln := range listeners {
		ln.Close()
	}
219
	snowflakes.End()
220
	log.Println("snowflake is done.")
221
}