Skip to content

feat(miniooni): implement torsf tunnel #921

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Oct 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 28 additions & 20 deletions internal/cmd/miniooni/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,26 @@ import (

// Options contains the options you can set from the CLI.
type Options struct {
Annotations []string
Emoji bool
ExtraOptions []string
HomeDir string
Inputs []string
InputFilePaths []string
MaxRuntime int64
NoJSON bool
NoCollector bool
ProbeServicesURL string
Proxy string
Random bool
RepeatEvery int64
ReportFile string
TorArgs []string
TorBinary string
Tunnel string
Verbose bool
Yes bool
Annotations []string
Emoji bool
ExtraOptions []string
HomeDir string
Inputs []string
InputFilePaths []string
MaxRuntime int64
NoJSON bool
NoCollector bool
ProbeServicesURL string
Proxy string
Random bool
RepeatEvery int64
ReportFile string
SnowflakeRendezvous string
TorArgs []string
TorBinary string
Tunnel string
Verbose bool
Yes bool
}

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

flags.StringVar(
&globalOptions.SnowflakeRendezvous,
"snowflake-rendezvous",
"domain_fronting",
"rendezvous method for --tunnel=torsf (one of: \"domain_fronting\" and \"amp\")",
)

flags.StringSliceVar(
&globalOptions.TorArgs,
"tor-args",
Expand All @@ -143,7 +151,7 @@ func main() {
&globalOptions.Tunnel,
"tunnel",
"",
"tunnel to use to communicate with the OONI backend (one of: tor, psiphon)",
"tunnel to use to communicate with the OONI backend (one of: psiphon, tor, torsf)",
)

flags.BoolVarP(
Expand Down
17 changes: 9 additions & 8 deletions internal/cmd/miniooni/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,15 @@ func newSessionOrPanic(ctx context.Context, currentOptions *Options,
runtimex.PanicOnError(err, "cannot create tunnelDir")

config := engine.SessionConfig{
KVStore: kvstore,
Logger: logger,
ProxyURL: proxyURL,
SoftwareName: softwareName,
SoftwareVersion: softwareVersion,
TorArgs: currentOptions.TorArgs,
TorBinary: currentOptions.TorBinary,
TunnelDir: tunnelDir,
KVStore: kvstore,
Logger: logger,
ProxyURL: proxyURL,
SnowflakeRendezvous: currentOptions.SnowflakeRendezvous,
SoftwareName: softwareName,
SoftwareVersion: softwareVersion,
TorArgs: currentOptions.TorArgs,
TorBinary: currentOptions.TorBinary,
TunnelDir: tunnelDir,
}
if currentOptions.ProbeServicesURL != "" {
config.AvailableProbeServices = []model.OOAPIService{{
Expand Down
19 changes: 12 additions & 7 deletions internal/engine/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ type SessionConfig struct {
TorArgs []string
TorBinary string

// SnowflakeRendezvous is the rendezvous method
// to be used by the torsf tunnel
SnowflakeRendezvous string

// TunnelDir is the directory where we should store
// the state of persistent tunnels. This field is
// optional _unless_ you want to use tunnels. In such
Expand Down Expand Up @@ -171,16 +175,17 @@ func NewSession(ctx context.Context, config SessionConfig) (*Session, error) {
proxyURL := config.ProxyURL
if proxyURL != nil {
switch proxyURL.Scheme {
case "psiphon", "tor", "fake":
case "psiphon", "tor", "torsf", "fake":
config.Logger.Infof(
"starting '%s' tunnel; please be patient...", proxyURL.Scheme)
tunnel, _, err := tunnel.Start(ctx, &tunnel.Config{
Logger: config.Logger,
Name: proxyURL.Scheme,
Session: &sessionTunnelEarlySession{},
TorArgs: config.TorArgs,
TorBinary: config.TorBinary,
TunnelDir: config.TunnelDir,
Logger: config.Logger,
Name: proxyURL.Scheme,
SnowflakeRendezvous: config.SnowflakeRendezvous,
Session: &sessionTunnelEarlySession{},
TorArgs: config.TorArgs,
TorBinary: config.TorBinary,
TunnelDir: config.TunnelDir,
})
if err != nil {
return nil, err
Expand Down
37 changes: 19 additions & 18 deletions internal/ptx/ptx.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ type Listener struct {
// counts the bytes consumed by the experiment.
ExperimentByteCounter *bytecounter.Counter

// ListenSocks is OPTIONAL and allows you to override the
// function called by default to listen for SOCKS5.
ListenSocks func(network string, laddr string) (SocksListener, error)

// Logger is the OPTIONAL logger. When not set, this library
// will not emit logs. (But the underlying pluggable transport
// may still emit its own log messages.)
Expand All @@ -98,10 +102,7 @@ type Listener struct {
laddr net.Addr

// listener allows us to stop the listener.
listener ptxSocksListener

// overrideListenSocks allows us to override pt.ListenSocks.
overrideListenSocks func(network string, laddr string) (ptxSocksListener, error)
listener SocksListener
}

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

// ptxSocksListener is a pt.SocksListener-like structure.
type ptxSocksListener interface {
// SocksListener is the listener for socks connections.
type SocksListener interface {
// AcceptSocks accepts a socks conn
AcceptSocks() (ptxSocksConn, error)
AcceptSocks() (SocksConn, error)

// Addr returns the listening address.
Addr() net.Addr
Expand All @@ -181,8 +182,8 @@ type ptxSocksListener interface {
Close() error
}

// ptxSocksConn is a pt.SocksConn-like structure.
type ptxSocksConn interface {
// SocksConn is a SOCKS connection.
type SocksConn interface {
// net.Conn is the embedded interface.
net.Conn

Expand All @@ -192,7 +193,7 @@ type ptxSocksConn interface {

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

// listenSocks calles either pt.ListenSocks or lst.overrideListenSocks.
func (lst *Listener) listenSocks(network string, laddr string) (ptxSocksListener, error) {
if lst.overrideListenSocks != nil {
return lst.overrideListenSocks(network, laddr)
func (lst *Listener) listenSocks(network string, laddr string) (SocksListener, error) {
if lst.ListenSocks != nil {
return lst.ListenSocks(network, laddr)
}
return lst.castListener(pt.ListenSocks(network, laddr))
}

// castListener casts a pt.SocksListener to ptxSocksListener.
func (lst *Listener) castListener(in *pt.SocksListener, err error) (ptxSocksListener, error) {
func (lst *Listener) castListener(in *pt.SocksListener, err error) (SocksListener, error) {
if err != nil {
return nil, err
}
Expand All @@ -264,7 +265,7 @@ type ptxSocksListenerAdapter struct {
}

// AcceptSocks adapts pt.SocksListener.AcceptSocks to ptxSockListener.AcceptSocks.
func (la *ptxSocksListenerAdapter) AcceptSocks() (ptxSocksConn, error) {
func (la *ptxSocksListenerAdapter) AcceptSocks() (SocksConn, error) {
return la.SocksListener.AcceptSocks()
}

Expand Down Expand Up @@ -307,11 +308,11 @@ func (lst *Listener) Stop() {
// Assuming that we are listening at 127.0.0.1:12345, then this
// function will return the following string:
//
// obfs4 socks5 127.0.0.1:12345
// obfs4 socks5 127.0.0.1:12345
//
// The correct configuration line for the `torrc` would be:
//
// ClientTransportPlugin obfs4 socks5 127.0.0.1:12345
// ClientTransportPlugin obfs4 socks5 127.0.0.1:12345
//
// Since we pass configuration to tor using the command line, it
// is more convenient to us to avoid including ClientTransportPlugin
Expand Down
8 changes: 4 additions & 4 deletions internal/ptx/ptx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestListenerWorksWithFakeDialer(t *testing.T) {
func TestListenerCannotListen(t *testing.T) {
expected := errors.New("mocked error")
lst := &Listener{
overrideListenSocks: func(network, laddr string) (ptxSocksListener, error) {
ListenSocks: func(network, laddr string) (SocksListener, error) {
return nil, expected
},
}
Expand Down Expand Up @@ -193,7 +193,7 @@ func TestListenerForwardWithNaturalTermination(t *testing.T) {
// mockableSocksListener is a mockable ptxSocksListener.
type mockableSocksListener struct {
// MockAcceptSocks allows to mock AcceptSocks.
MockAcceptSocks func() (ptxSocksConn, error)
MockAcceptSocks func() (SocksConn, error)

// MockAddr allows to mock Addr.
MockAddr func() net.Addr
Expand All @@ -203,7 +203,7 @@ type mockableSocksListener struct {
}

// AcceptSocks implemements ptxSocksListener.AcceptSocks.
func (m *mockableSocksListener) AcceptSocks() (ptxSocksConn, error) {
func (m *mockableSocksListener) AcceptSocks() (SocksConn, error) {
return m.MockAcceptSocks()
}

Expand All @@ -220,7 +220,7 @@ func (m *mockableSocksListener) Close() error {
func TestListenerLoopWithTemporaryError(t *testing.T) {
isclosed := &atomicx.Int64{}
sl := &mockableSocksListener{
MockAcceptSocks: func() (ptxSocksConn, error) {
MockAcceptSocks: func() (SocksConn, error) {
if isclosed.Load() > 0 {
return nil, io.EOF
}
Expand Down
25 changes: 25 additions & 0 deletions internal/tunnel/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/cretz/bine/control"
"github.com/cretz/bine/tor"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/ptx"
"golang.org/x/sys/execabs"
)

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

// SnowflakeRendezvous is the OPTIONAL rendezvous
// method for snowflake
SnowflakeRendezvous string

// TunnelDir is the MANDATORY directory in which the tunnel SHOULD
// store its state, if any. If this field is empty, the
// Start function fails with ErrEmptyTunnelDir.
Expand Down Expand Up @@ -56,6 +61,18 @@ type Config struct {
// testNetListen allows us to mock net.Listen in testing code.
testNetListen func(network string, address string) (net.Listener, error)

// testSfListenSocks is OPTIONAL and allows to override the
// ListenSocks field of a ptx.Listener.
testSfListenSocks func(network string, laddr string) (ptx.SocksListener, error)

// testSfNewPTXListener is OPTIONAL and allows us to wrap the
// constructed ptx.Listener for testing purposes.
testSfWrapPTXListener func(torsfPTXListener) torsfPTXListener

// testSfTorStart is OPTIONAL and allows us to override the
// call to torStart inside the torsf tunnel.
testSfTorStart func(ctx context.Context, config *Config) (Tunnel, DebugInfo, error)

// testSocks5New allows us to mock socks5.New in testing code.
testSocks5New func(conf *socks5.Config) (*socks5.Server, error)

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

// snowflakeRendezvousMethod returns the rendezvous method that snowflake should use
func (c *Config) snowflakeRendezvousMethod() string {
if c.SnowflakeRendezvous != "" {
return c.SnowflakeRendezvous
}
return "domain_fronting"
}

// logger returns the logger to use.
func (c *Config) logger() model.Logger {
if c.Logger != nil {
Expand Down
Loading