/
snowflake.go
208 lines (176 loc) · 6.15 KB
/
snowflake.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package ptx
import (
"context"
"errors"
"net"
"github.com/ooni/probe-engine/pkg/stuninput"
sflib "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/client/lib"
)
// SnowflakeRendezvousMethod is the method which with we perform the rendezvous.
type SnowflakeRendezvousMethod interface {
// Name is the name of the method.
Name() string
// AMPCacheURL returns a suitable AMP cache URL.
AMPCacheURL() string
// BrokerURL returns a suitable broker URL.
BrokerURL() string
// FrontDomain returns a suitable front domain.
FrontDomain() string
}
// NewSnowflakeRendezvousMethodDomainFronting is a rendezvous method
// that uses domain fronting to perform the rendezvous.
func NewSnowflakeRendezvousMethodDomainFronting() SnowflakeRendezvousMethod {
return &snowflakeRendezvousMethodDomainFronting{}
}
type snowflakeRendezvousMethodDomainFronting struct{}
func (d *snowflakeRendezvousMethodDomainFronting) Name() string {
return "domain_fronting"
}
func (d *snowflakeRendezvousMethodDomainFronting) AMPCacheURL() string {
return ""
}
func (d *snowflakeRendezvousMethodDomainFronting) BrokerURL() string {
return "https://snowflake-broker.torproject.net.global.prod.fastly.net/"
}
func (d *snowflakeRendezvousMethodDomainFronting) FrontDomain() string {
return "foursquare.com"
}
// NewSnowflakeRendezvousMethodAMP is a rendezvous method that
// uses the AMP cache to perform the rendezvous.
func NewSnowflakeRendezvousMethodAMP() SnowflakeRendezvousMethod {
return &snowflakeRendezvousMethodAMP{}
}
type snowflakeRendezvousMethodAMP struct{}
func (d *snowflakeRendezvousMethodAMP) Name() string {
return "amp"
}
func (d *snowflakeRendezvousMethodAMP) AMPCacheURL() string {
return "https://cdn.ampproject.org/"
}
func (d *snowflakeRendezvousMethodAMP) BrokerURL() string {
return "https://snowflake-broker.torproject.net/"
}
func (d *snowflakeRendezvousMethodAMP) FrontDomain() string {
return "www.google.com"
}
// ErrSnowflakeNoSuchRendezvousMethod indicates the given rendezvous
// method is not supported by this implementation.
var ErrSnowflakeNoSuchRendezvousMethod = errors.New("ptx: unsupported rendezvous method")
// NewSnowflakeRendezvousMethod creates a new rendezvous method by name. We currently
// support the following rendezvous methods:
//
// 1. "domain_fronting" uses domain fronting with the sstatic.net CDN;
//
// 2. "" means default and it is currently equivalent to "domain_fronting" (but
// we don't guarantee that this default may change over time);
//
// 3. "amp" uses the AMP cache.
//
// Returns either a valid rendezvous method or an error.
func NewSnowflakeRendezvousMethod(method string) (SnowflakeRendezvousMethod, error) {
switch method {
case "domain_fronting", "":
return NewSnowflakeRendezvousMethodDomainFronting(), nil
case "amp":
return NewSnowflakeRendezvousMethodAMP(), nil
default:
return nil, ErrSnowflakeNoSuchRendezvousMethod
}
}
// SnowflakeDialer is a dialer for snowflake. You SHOULD either use a factory
// for constructing this type or set the fields marked as MANDATORY.
type SnowflakeDialer struct {
// RendezvousMethod is the MANDATORY rendezvous method to use.
RendezvousMethod SnowflakeRendezvousMethod
// newClientTransport is an OPTIONAL hook for creating
// an alternative snowflakeTransport in testing.
newClientTransport func(config sflib.ClientConfig) (snowflakeTransport, error)
}
// NewSnowflakeDialer creates a SnowflakeDialer with default settings.
func NewSnowflakeDialer() *SnowflakeDialer {
return &SnowflakeDialer{
RendezvousMethod: NewSnowflakeRendezvousMethodDomainFronting(),
newClientTransport: nil,
}
}
// NewSnowflakeDialerWithRendezvousMethod creates a SnowflakeDialer
// using the given RendezvousMethod explicitly.
func NewSnowflakeDialerWithRendezvousMethod(m SnowflakeRendezvousMethod) *SnowflakeDialer {
return &SnowflakeDialer{
RendezvousMethod: m,
newClientTransport: nil,
}
}
// snowflakeTransport is anything that allows us to dial a snowflake
type snowflakeTransport interface {
Dial() (net.Conn, error)
}
// DialContext establishes a connection with the given SF proxy. The context
// argument allows to interrupt this operation midway.
func (d *SnowflakeDialer) DialContext(ctx context.Context) (net.Conn, error) {
conn, _, err := d.dialContext(ctx)
return conn, err
}
func (d *SnowflakeDialer) dialContext(
ctx context.Context) (net.Conn, chan interface{}, error) {
done := make(chan interface{})
txp, err := d.newSnowflakeClient(sflib.ClientConfig{
BrokerURL: d.RendezvousMethod.BrokerURL(),
AmpCacheURL: d.RendezvousMethod.AMPCacheURL(),
FrontDomain: d.RendezvousMethod.FrontDomain(),
ICEAddresses: d.iceAddresses(),
KeepLocalAddresses: false,
Max: d.maxSnowflakes(),
})
if err != nil {
return nil, nil, err
}
connch, errch := make(chan net.Conn), make(chan error, 1)
go func() {
defer close(done) // allow tests to synchronize with this goroutine's exit
conn, err := txp.Dial()
if err != nil {
errch <- err // buffered channel
return
}
select {
case connch <- conn:
default:
conn.Close() // context won the race
}
}()
select {
case conn := <-connch:
return conn, done, nil
case err := <-errch:
return nil, done, err
case <-ctx.Done():
return nil, done, ctx.Err()
}
}
// newSnowflakeClient allows us to call a mock rather than
// the real sflib.NewSnowflakeClient.
func (d *SnowflakeDialer) newSnowflakeClient(
config sflib.ClientConfig) (snowflakeTransport, error) {
if d.newClientTransport != nil {
return d.newClientTransport(config)
}
return sflib.NewSnowflakeClient(config)
}
// iceAddresses returns suitable ICE addresses.
func (d *SnowflakeDialer) iceAddresses() []string {
return stuninput.AsSnowflakeInput()
}
// maxSnowflakes returns the number of snowflakes to collect.
func (d *SnowflakeDialer) maxSnowflakes() int {
return 1
}
// AsBridgeArgument returns the argument to be passed to
// the tor command line to declare this bridge.
func (d *SnowflakeDialer) AsBridgeArgument() string {
return "snowflake 192.0.2.3:1 2B280B23E1107BB62ABFC40DDCC8824814F80A72"
}
// Name returns the pluggable transport name.
func (d *SnowflakeDialer) Name() string {
return "snowflake"
}