Skip to content

Commit 18a9523

Browse files
authored
feat(miniooni): implement torsf tunnel (#921)
This diff adds to miniooni support for using the torsf tunnel. Such a tunnel consists of a snowflake pluggable transport in front of a custom instance of tor and requires tor to be installed. The usage is like: ``` ./miniooni --tunnel=torsf [...] ``` The default snowflake rendezvous method is "domain_fronting". You can select the AMP cache instead using "amp": ``` ./miniooni --snowflake-rendezvous=amp --tunnel=torsf [...] ``` Part of ooni/probe#1955
1 parent 5466f30 commit 18a9523

File tree

10 files changed

+438
-57
lines changed

10 files changed

+438
-57
lines changed

internal/cmd/miniooni/main.go

+28-20
Original file line numberDiff line numberDiff line change
@@ -25,25 +25,26 @@ import (
2525

2626
// Options contains the options you can set from the CLI.
2727
type Options struct {
28-
Annotations []string
29-
Emoji bool
30-
ExtraOptions []string
31-
HomeDir string
32-
Inputs []string
33-
InputFilePaths []string
34-
MaxRuntime int64
35-
NoJSON bool
36-
NoCollector bool
37-
ProbeServicesURL string
38-
Proxy string
39-
Random bool
40-
RepeatEvery int64
41-
ReportFile string
42-
TorArgs []string
43-
TorBinary string
44-
Tunnel string
45-
Verbose bool
46-
Yes bool
28+
Annotations []string
29+
Emoji bool
30+
ExtraOptions []string
31+
HomeDir string
32+
Inputs []string
33+
InputFilePaths []string
34+
MaxRuntime int64
35+
NoJSON bool
36+
NoCollector bool
37+
ProbeServicesURL string
38+
Proxy string
39+
Random bool
40+
RepeatEvery int64
41+
ReportFile string
42+
SnowflakeRendezvous string
43+
TorArgs []string
44+
TorBinary string
45+
Tunnel string
46+
Verbose bool
47+
Yes bool
4748
}
4849

4950
// main is the main function of miniooni.
@@ -125,6 +126,13 @@ func main() {
125126
"set the output report file path (default: \"report.jsonl\")",
126127
)
127128

129+
flags.StringVar(
130+
&globalOptions.SnowflakeRendezvous,
131+
"snowflake-rendezvous",
132+
"domain_fronting",
133+
"rendezvous method for --tunnel=torsf (one of: \"domain_fronting\" and \"amp\")",
134+
)
135+
128136
flags.StringSliceVar(
129137
&globalOptions.TorArgs,
130138
"tor-args",
@@ -143,7 +151,7 @@ func main() {
143151
&globalOptions.Tunnel,
144152
"tunnel",
145153
"",
146-
"tunnel to use to communicate with the OONI backend (one of: tor, psiphon)",
154+
"tunnel to use to communicate with the OONI backend (one of: psiphon, tor, torsf)",
147155
)
148156

149157
flags.BoolVarP(

internal/cmd/miniooni/session.go

+9-8
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,15 @@ func newSessionOrPanic(ctx context.Context, currentOptions *Options,
3636
runtimex.PanicOnError(err, "cannot create tunnelDir")
3737

3838
config := engine.SessionConfig{
39-
KVStore: kvstore,
40-
Logger: logger,
41-
ProxyURL: proxyURL,
42-
SoftwareName: softwareName,
43-
SoftwareVersion: softwareVersion,
44-
TorArgs: currentOptions.TorArgs,
45-
TorBinary: currentOptions.TorBinary,
46-
TunnelDir: tunnelDir,
39+
KVStore: kvstore,
40+
Logger: logger,
41+
ProxyURL: proxyURL,
42+
SnowflakeRendezvous: currentOptions.SnowflakeRendezvous,
43+
SoftwareName: softwareName,
44+
SoftwareVersion: softwareVersion,
45+
TorArgs: currentOptions.TorArgs,
46+
TorBinary: currentOptions.TorBinary,
47+
TunnelDir: tunnelDir,
4748
}
4849
if currentOptions.ProbeServicesURL != "" {
4950
config.AvailableProbeServices = []model.OOAPIService{{

internal/engine/session.go

+12-7
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ type SessionConfig struct {
3535
TorArgs []string
3636
TorBinary string
3737

38+
// SnowflakeRendezvous is the rendezvous method
39+
// to be used by the torsf tunnel
40+
SnowflakeRendezvous string
41+
3842
// TunnelDir is the directory where we should store
3943
// the state of persistent tunnels. This field is
4044
// optional _unless_ you want to use tunnels. In such
@@ -171,16 +175,17 @@ func NewSession(ctx context.Context, config SessionConfig) (*Session, error) {
171175
proxyURL := config.ProxyURL
172176
if proxyURL != nil {
173177
switch proxyURL.Scheme {
174-
case "psiphon", "tor", "fake":
178+
case "psiphon", "tor", "torsf", "fake":
175179
config.Logger.Infof(
176180
"starting '%s' tunnel; please be patient...", proxyURL.Scheme)
177181
tunnel, _, err := tunnel.Start(ctx, &tunnel.Config{
178-
Logger: config.Logger,
179-
Name: proxyURL.Scheme,
180-
Session: &sessionTunnelEarlySession{},
181-
TorArgs: config.TorArgs,
182-
TorBinary: config.TorBinary,
183-
TunnelDir: config.TunnelDir,
182+
Logger: config.Logger,
183+
Name: proxyURL.Scheme,
184+
SnowflakeRendezvous: config.SnowflakeRendezvous,
185+
Session: &sessionTunnelEarlySession{},
186+
TorArgs: config.TorArgs,
187+
TorBinary: config.TorBinary,
188+
TunnelDir: config.TunnelDir,
184189
})
185190
if err != nil {
186191
return nil, err

internal/ptx/ptx.go

+19-18
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ type Listener struct {
7474
// counts the bytes consumed by the experiment.
7575
ExperimentByteCounter *bytecounter.Counter
7676

77+
// ListenSocks is OPTIONAL and allows you to override the
78+
// function called by default to listen for SOCKS5.
79+
ListenSocks func(network string, laddr string) (SocksListener, error)
80+
7781
// Logger is the OPTIONAL logger. When not set, this library
7882
// will not emit logs. (But the underlying pluggable transport
7983
// may still emit its own log messages.)
@@ -98,10 +102,7 @@ type Listener struct {
98102
laddr net.Addr
99103

100104
// listener allows us to stop the listener.
101-
listener ptxSocksListener
102-
103-
// overrideListenSocks allows us to override pt.ListenSocks.
104-
overrideListenSocks func(network string, laddr string) (ptxSocksListener, error)
105+
listener SocksListener
105106
}
106107

107108
// logger returns the Logger, if set, or the defaultLogger.
@@ -148,7 +149,7 @@ func (lst *Listener) forwardWithContext(ctx context.Context, left, right net.Con
148149
// handleSocksConn handles a new SocksConn connection by establishing
149150
// the corresponding PT connection and forwarding traffic. This
150151
// function TAKES OWNERSHIP of the socksConn argument.
151-
func (lst *Listener) handleSocksConn(ctx context.Context, socksConn ptxSocksConn) error {
152+
func (lst *Listener) handleSocksConn(ctx context.Context, socksConn SocksConn) error {
152153
err := socksConn.Grant(&net.TCPAddr{IP: net.IPv4zero, Port: 0})
153154
if err != nil {
154155
lst.logger().Warnf("ptx: socksConn.Grant error: %s", err)
@@ -169,10 +170,10 @@ func (lst *Listener) handleSocksConn(ctx context.Context, socksConn ptxSocksConn
169170
return nil // used for testing
170171
}
171172

172-
// ptxSocksListener is a pt.SocksListener-like structure.
173-
type ptxSocksListener interface {
173+
// SocksListener is the listener for socks connections.
174+
type SocksListener interface {
174175
// AcceptSocks accepts a socks conn
175-
AcceptSocks() (ptxSocksConn, error)
176+
AcceptSocks() (SocksConn, error)
176177

177178
// Addr returns the listening address.
178179
Addr() net.Addr
@@ -181,8 +182,8 @@ type ptxSocksListener interface {
181182
Close() error
182183
}
183184

184-
// ptxSocksConn is a pt.SocksConn-like structure.
185-
type ptxSocksConn interface {
185+
// SocksConn is a SOCKS connection.
186+
type SocksConn interface {
186187
// net.Conn is the embedded interface.
187188
net.Conn
188189

@@ -192,7 +193,7 @@ type ptxSocksConn interface {
192193

193194
// acceptLoop accepts and handles local socks connection. This function
194195
// DOES NOT take ownership of the socks listener.
195-
func (lst *Listener) acceptLoop(ctx context.Context, ln ptxSocksListener) {
196+
func (lst *Listener) acceptLoop(ctx context.Context, ln SocksListener) {
196197
for {
197198
conn, err := ln.AcceptSocks()
198199
if err != nil {
@@ -243,15 +244,15 @@ func (lst *Listener) Start() error {
243244
}
244245

245246
// listenSocks calles either pt.ListenSocks or lst.overrideListenSocks.
246-
func (lst *Listener) listenSocks(network string, laddr string) (ptxSocksListener, error) {
247-
if lst.overrideListenSocks != nil {
248-
return lst.overrideListenSocks(network, laddr)
247+
func (lst *Listener) listenSocks(network string, laddr string) (SocksListener, error) {
248+
if lst.ListenSocks != nil {
249+
return lst.ListenSocks(network, laddr)
249250
}
250251
return lst.castListener(pt.ListenSocks(network, laddr))
251252
}
252253

253254
// castListener casts a pt.SocksListener to ptxSocksListener.
254-
func (lst *Listener) castListener(in *pt.SocksListener, err error) (ptxSocksListener, error) {
255+
func (lst *Listener) castListener(in *pt.SocksListener, err error) (SocksListener, error) {
255256
if err != nil {
256257
return nil, err
257258
}
@@ -264,7 +265,7 @@ type ptxSocksListenerAdapter struct {
264265
}
265266

266267
// AcceptSocks adapts pt.SocksListener.AcceptSocks to ptxSockListener.AcceptSocks.
267-
func (la *ptxSocksListenerAdapter) AcceptSocks() (ptxSocksConn, error) {
268+
func (la *ptxSocksListenerAdapter) AcceptSocks() (SocksConn, error) {
268269
return la.SocksListener.AcceptSocks()
269270
}
270271

@@ -307,11 +308,11 @@ func (lst *Listener) Stop() {
307308
// Assuming that we are listening at 127.0.0.1:12345, then this
308309
// function will return the following string:
309310
//
310-
// obfs4 socks5 127.0.0.1:12345
311+
// obfs4 socks5 127.0.0.1:12345
311312
//
312313
// The correct configuration line for the `torrc` would be:
313314
//
314-
// ClientTransportPlugin obfs4 socks5 127.0.0.1:12345
315+
// ClientTransportPlugin obfs4 socks5 127.0.0.1:12345
315316
//
316317
// Since we pass configuration to tor using the command line, it
317318
// is more convenient to us to avoid including ClientTransportPlugin

internal/ptx/ptx_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func TestListenerWorksWithFakeDialer(t *testing.T) {
7373
func TestListenerCannotListen(t *testing.T) {
7474
expected := errors.New("mocked error")
7575
lst := &Listener{
76-
overrideListenSocks: func(network, laddr string) (ptxSocksListener, error) {
76+
ListenSocks: func(network, laddr string) (SocksListener, error) {
7777
return nil, expected
7878
},
7979
}
@@ -193,7 +193,7 @@ func TestListenerForwardWithNaturalTermination(t *testing.T) {
193193
// mockableSocksListener is a mockable ptxSocksListener.
194194
type mockableSocksListener struct {
195195
// MockAcceptSocks allows to mock AcceptSocks.
196-
MockAcceptSocks func() (ptxSocksConn, error)
196+
MockAcceptSocks func() (SocksConn, error)
197197

198198
// MockAddr allows to mock Addr.
199199
MockAddr func() net.Addr
@@ -203,7 +203,7 @@ type mockableSocksListener struct {
203203
}
204204

205205
// AcceptSocks implemements ptxSocksListener.AcceptSocks.
206-
func (m *mockableSocksListener) AcceptSocks() (ptxSocksConn, error) {
206+
func (m *mockableSocksListener) AcceptSocks() (SocksConn, error) {
207207
return m.MockAcceptSocks()
208208
}
209209

@@ -220,7 +220,7 @@ func (m *mockableSocksListener) Close() error {
220220
func TestListenerLoopWithTemporaryError(t *testing.T) {
221221
isclosed := &atomicx.Int64{}
222222
sl := &mockableSocksListener{
223-
MockAcceptSocks: func() (ptxSocksConn, error) {
223+
MockAcceptSocks: func() (SocksConn, error) {
224224
if isclosed.Load() > 0 {
225225
return nil, io.EOF
226226
}

internal/tunnel/config.go

+25
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/cretz/bine/control"
1111
"github.com/cretz/bine/tor"
1212
"github.com/ooni/probe-cli/v3/internal/model"
13+
"github.com/ooni/probe-cli/v3/internal/ptx"
1314
"golang.org/x/sys/execabs"
1415
)
1516

@@ -28,6 +29,10 @@ type Config struct {
2829
// of obtaining a valid psiphon configuration.
2930
Session Session
3031

32+
// SnowflakeRendezvous is the OPTIONAL rendezvous
33+
// method for snowflake
34+
SnowflakeRendezvous string
35+
3136
// TunnelDir is the MANDATORY directory in which the tunnel SHOULD
3237
// store its state, if any. If this field is empty, the
3338
// Start function fails with ErrEmptyTunnelDir.
@@ -56,6 +61,18 @@ type Config struct {
5661
// testNetListen allows us to mock net.Listen in testing code.
5762
testNetListen func(network string, address string) (net.Listener, error)
5863

64+
// testSfListenSocks is OPTIONAL and allows to override the
65+
// ListenSocks field of a ptx.Listener.
66+
testSfListenSocks func(network string, laddr string) (ptx.SocksListener, error)
67+
68+
// testSfNewPTXListener is OPTIONAL and allows us to wrap the
69+
// constructed ptx.Listener for testing purposes.
70+
testSfWrapPTXListener func(torsfPTXListener) torsfPTXListener
71+
72+
// testSfTorStart is OPTIONAL and allows us to override the
73+
// call to torStart inside the torsf tunnel.
74+
testSfTorStart func(ctx context.Context, config *Config) (Tunnel, DebugInfo, error)
75+
5976
// testSocks5New allows us to mock socks5.New in testing code.
6077
testSocks5New func(conf *socks5.Config) (*socks5.Server, error)
6178

@@ -74,6 +91,14 @@ type Config struct {
7491
testTorGetInfo func(ctrl *control.Conn, keys ...string) ([]*control.KeyVal, error)
7592
}
7693

94+
// snowflakeRendezvousMethod returns the rendezvous method that snowflake should use
95+
func (c *Config) snowflakeRendezvousMethod() string {
96+
if c.SnowflakeRendezvous != "" {
97+
return c.SnowflakeRendezvous
98+
}
99+
return "domain_fronting"
100+
}
101+
77102
// logger returns the logger to use.
78103
func (c *Config) logger() model.Logger {
79104
if c.Logger != nil {

0 commit comments

Comments
 (0)