snowflake.go 5.75 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
	logToStateDir := flag.Bool("logToStateDir", false, "resolve the log file relative to tor's pt state dir")
93
	keepLocalAddresses := flag.Bool("keepLocalAddresses", 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
	flag.Parse()

99
	log.SetFlags(log.LstdFlags | log.LUTC)
100

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

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

132
	iceServers := parseIceServers(*iceServersCommas)
133
134
135
136
	log.Printf("Using ICE servers:")
	for _, server := range iceServers {
		log.Printf("url: %v", strings.Join(server.URLs, " "))
	}
137

138
	// Prepare to collect remote WebRTC peers.
139
	snowflakes := sf.NewPeers(*max)
Arlo Breault's avatar
Arlo Breault committed
140
141

	// Use potentially domain-fronting broker to rendezvous.
142
	broker, err := sf.NewBrokerChannel(*brokerURL, *frontDomain, sf.CreateBrokerTransport(), *keepLocalAddresses)
143
144
145
	if err != nil {
		log.Fatalf("parsing broker URL: %v", err)
	}
Arlo Breault's avatar
Arlo Breault committed
146
147
	snowflakes.Tongue = sf.NewWebRTCDialer(broker, iceServers)

148
	// Use a real logger to periodically output how much traffic is happening.
149
150
151
152
153
154
155
	snowflakes.BytesLogger = &sf.BytesSyncLogger{
		InboundChan:  make(chan int, 5),
		OutboundChan: make(chan int, 5),
		Inbound:      0,
		Outbound:     0,
		InEvents:     0,
		OutEvents:    0,
156
	}
157
	go snowflakes.BytesLogger.Log()
158

159
	go ConnectLoop(snowflakes)
160

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

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

193
194
195
196
	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() {
197
198
199
			if _, err := io.Copy(ioutil.Discard, os.Stdin); err != nil {
				log.Printf("calling io.Copy(ioutil.Discard, os.Stdin) returned error: %v", err)
			}
200
201
202
203
204
			log.Printf("synthesizing SIGTERM because of stdin close")
			sigChan <- syscall.SIGTERM
		}()
	}

David Fifield's avatar
David Fifield committed
205
	// Wait for a signal.
206
	<-sigChan
207

David Fifield's avatar
David Fifield committed
208
	// Signal received, shut down.
209
210
211
	for _, ln := range listeners {
		ln.Close()
	}
212
	snowflakes.End()
213
	log.Println("snowflake is done.")
214
}