refactor(tunnel): remove nil tunnels hack (#296)

* refactor(tunnel): remove nil tunnels hack

This code was originally introduced because a tunnel could be
nil in session.go. I have verified that every invocation of
tunnel.Start is careful to ensure that we have a tunnel name
and that we don't manipulate a nil tunnel.

For this reason, I'd rather remove this tricky bit of code and
further simplify the tunnel code.

Part of https://github.com/ooni/probe/issues/985

* even better docs
This commit is contained in:
Simone Basso 2021-04-05 16:08:16 +02:00 committed by GitHub
commit 2bafb179c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 69 additions and 82 deletions

View file

@ -1,5 +1,26 @@
// Package tunnel allows to create tunnels to speak
// with OONI backends and other services.
//
// You need to fill a Config object and call Start to
// obtain an instance of Tunnel. The tunnel will expose
// a SOCKS5 proxy. You need to configure your HTTP
// code to use such a proxy. Remember to call the Stop
// method of a tunnel when you are done.
//
// There are two use cases for this package. The first
// use case is to enable urlgetter to perform measurements
// over tunnels (mainly psiphon).
//
// The second use case is to use tunnels to reach to the
// OONI backend when it's blocked. For the latter case
// we currently mainly use psiphon. In such a case, we'll
// use a psiphon configuration embedded into the OONI
// binary itself. When you are running a version of OONI
// that does not embed such a configuration, it won't
// be possible to address this use case.
//
// See session.go in the engine package for more details
// concerning this second use case.
package tunnel
import (
@ -10,21 +31,27 @@ import (
"time"
)
// Session is the way in which this package sees a Session.
// Session is a measurement session. We filter for the only
// functionality we're interested to use. That is, fetching the
// psiphon configuration from the OONI backend (if possible).
type Session interface {
// FetchPsiphonConfig should fetch and return the psiphon config
// as a serialized JSON, or fail with an error.
FetchPsiphonConfig(ctx context.Context) ([]byte, error)
}
// Tunnel is a tunnel used by the session
// Tunnel is a tunnel for communicating with OONI backends
// (and other services) to circumvent blocking.
type Tunnel interface {
// BootstrapTime returns the time it required to
// create an instance of the tunnel
// create a new tunnel instance.
BootstrapTime() time.Duration
// SOCKS5ProxyURL returns the SOCSK5 proxy URL
// SOCKS5ProxyURL returns the SOCSK5 proxy URL.
SOCKS5ProxyURL() *url.URL
// Stop stops the tunnel. This method is idempotent.
// Stop stops the tunnel. You should not attempt to
// use any other tunnel method after Stop.
Stop()
}
@ -35,30 +62,29 @@ var ErrEmptyTunnelDir = errors.New("TunnelDir is empty")
// is not supported by this package.
var ErrUnsupportedTunnelName = errors.New("unsupported tunnel name")
// Start starts a new tunnel by name or returns an error. Note that if you
// pass to this function the "" tunnel, you get back nil, nil.
// Start starts a new tunnel by name or returns an error. We currently
// support the following tunnels:
//
// The "tor" tunnel requires the "tor" binary to be installed on
// your system. You can use config.TorArgs and config.TorBinary to
// select what binary to execute and with which arguments.
//
// The "psiphon" tunnel requires a configuration. Some builds of
// ooniprobe embed a configuration into the binary. When this
// is the case, the config.Session is a mocked object that just
// retuns such configuration.
//
// Otherwise, If there is no embedded psiphon configuration, the
// config.Session will must be an ordinary session. In such a
// case, fetching the Psiphon configuration from the backend may
// fail when the backend is not reachable.
func Start(ctx context.Context, config *Config) (Tunnel, error) {
switch config.Name {
case "":
return enforceNilContract(nil, nil)
case "psiphon":
tun, err := psiphonStart(ctx, config)
return enforceNilContract(tun, err)
return psiphonStart(ctx, config)
case "tor":
tun, err := torStart(ctx, config)
return enforceNilContract(tun, err)
return torStart(ctx, config)
default:
return nil, fmt.Errorf("%w: %s", ErrUnsupportedTunnelName, config.Name)
}
}
// enforceNilContract ensures that either the tunnel is nil
// or the error is nil.
func enforceNilContract(tun Tunnel, err error) (Tunnel, error) {
// TODO(bassosimone): we currently allow returning nil, nil but
// we want to change this to return a fake NilTunnel.
if err != nil {
return nil, err
}
return tun, nil
}