a9b3a3b3a5
* fix(tunnel): pass /absolute/path/to/tor to cretz/bine It seems cretz/bine is not aware of https://blog.golang.org/path-security for now. I am planning to send over a diff for that later today. In the meanwhile, do the right thing here, and make sure that we obtain the absolute path to the tor binary before we continue. This work is part of https://github.com/ooni/probe-engine/issues/283. * fix tests when tor is not installed
321 lines
8.5 KiB
Go
321 lines
8.5 KiB
Go
package tunnel
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"testing"
|
|
|
|
"github.com/cretz/bine/control"
|
|
"github.com/cretz/bine/tor"
|
|
"github.com/ooni/probe-cli/v3/internal/engine/internal/mockable"
|
|
)
|
|
|
|
// torCloser is used to mock a running tor process, which
|
|
// we abstract as a io.Closer in tor.go.
|
|
type torCloser struct {
|
|
counter int
|
|
}
|
|
|
|
// Close implements io.Closer.Close.
|
|
func (c *torCloser) Close() error {
|
|
c.counter++
|
|
return errors.New("mocked mocked mocked")
|
|
}
|
|
|
|
func TestTorTunnelNonNil(t *testing.T) {
|
|
closer := new(torCloser)
|
|
proxy := &url.URL{Scheme: "x", Host: "10.0.0.1:443"}
|
|
tun := &torTunnel{
|
|
bootstrapTime: 128,
|
|
instance: closer,
|
|
proxy: proxy,
|
|
}
|
|
if tun.BootstrapTime() != 128 {
|
|
t.Fatal("not the bootstrap time we expected")
|
|
}
|
|
if tun.SOCKS5ProxyURL() != proxy {
|
|
t.Fatal("not the url we expected")
|
|
}
|
|
tun.Stop()
|
|
if closer.counter != 1 {
|
|
t.Fatal("something went wrong while stopping the tunnel")
|
|
}
|
|
}
|
|
|
|
func TestTorWithCancelledContext(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // fail immediately
|
|
tun, err := torStart(ctx, &Config{
|
|
Session: &mockable.Session{},
|
|
TunnelDir: "testdata",
|
|
})
|
|
if !errors.Is(err, context.Canceled) {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if tun != nil {
|
|
t.Fatal("expected nil tunnel here")
|
|
}
|
|
}
|
|
|
|
func TestTorWithEmptyTunnelDir(t *testing.T) {
|
|
ctx := context.Background()
|
|
tun, err := torStart(ctx, &Config{
|
|
Session: &mockable.Session{},
|
|
TunnelDir: "",
|
|
})
|
|
if !errors.Is(err, ErrEmptyTunnelDir) {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if tun != nil {
|
|
t.Fatal("expected nil tunnel here")
|
|
}
|
|
}
|
|
|
|
func TestTorBinaryNotFoundFailure(t *testing.T) {
|
|
ctx := context.Background()
|
|
tun, err := torStart(ctx, &Config{
|
|
Session: &mockable.Session{},
|
|
TorBinary: "/nonexistent/directory/tor",
|
|
TunnelDir: "testdata",
|
|
})
|
|
if !errors.Is(err, syscall.ENOENT) {
|
|
t.Fatal("not the error we expected", err)
|
|
}
|
|
if tun != nil {
|
|
t.Fatal("expected nil tunnel here")
|
|
}
|
|
}
|
|
|
|
func TestTorStartFailure(t *testing.T) {
|
|
expected := errors.New("mocked error")
|
|
ctx := context.Background()
|
|
tun, err := torStart(ctx, &Config{
|
|
Session: &mockable.Session{},
|
|
TunnelDir: "testdata",
|
|
testExecabsLookPath: func(name string) (string, error) {
|
|
return "/usr/local/bin/tor", nil
|
|
},
|
|
testTorStart: func(ctx context.Context, conf *tor.StartConf) (*tor.Tor, error) {
|
|
return nil, expected
|
|
},
|
|
})
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if tun != nil {
|
|
t.Fatal("expected nil tunnel here")
|
|
}
|
|
}
|
|
|
|
func TestTorEnableNetworkFailure(t *testing.T) {
|
|
expected := errors.New("mocked error")
|
|
ctx := context.Background()
|
|
tun, err := torStart(ctx, &Config{
|
|
Session: &mockable.Session{},
|
|
TunnelDir: "testdata",
|
|
testExecabsLookPath: func(name string) (string, error) {
|
|
return "/usr/local/bin/tor", nil
|
|
},
|
|
testTorStart: func(ctx context.Context, conf *tor.StartConf) (*tor.Tor, error) {
|
|
return &tor.Tor{}, nil
|
|
},
|
|
testTorEnableNetwork: func(ctx context.Context, tor *tor.Tor, wait bool) error {
|
|
return expected
|
|
},
|
|
})
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if tun != nil {
|
|
t.Fatal("expected nil tunnel here")
|
|
}
|
|
}
|
|
|
|
func TestTorGetInfoFailure(t *testing.T) {
|
|
expected := errors.New("mocked error")
|
|
ctx := context.Background()
|
|
tun, err := torStart(ctx, &Config{
|
|
Session: &mockable.Session{},
|
|
TunnelDir: "testdata",
|
|
testExecabsLookPath: func(name string) (string, error) {
|
|
return "/usr/local/bin/tor", nil
|
|
},
|
|
testTorStart: func(ctx context.Context, conf *tor.StartConf) (*tor.Tor, error) {
|
|
return &tor.Tor{}, nil
|
|
},
|
|
testTorEnableNetwork: func(ctx context.Context, tor *tor.Tor, wait bool) error {
|
|
return nil
|
|
},
|
|
testTorGetInfo: func(ctrl *control.Conn, keys ...string) ([]*control.KeyVal, error) {
|
|
return nil, expected
|
|
},
|
|
})
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if tun != nil {
|
|
t.Fatal("expected nil tunnel here")
|
|
}
|
|
}
|
|
|
|
func TestTorGetInfoInvalidNumberOfKeys(t *testing.T) {
|
|
ctx := context.Background()
|
|
tun, err := torStart(ctx, &Config{
|
|
Session: &mockable.Session{},
|
|
TunnelDir: "testdata",
|
|
testExecabsLookPath: func(name string) (string, error) {
|
|
return "/usr/local/bin/tor", nil
|
|
},
|
|
testTorStart: func(ctx context.Context, conf *tor.StartConf) (*tor.Tor, error) {
|
|
return &tor.Tor{}, nil
|
|
},
|
|
testTorEnableNetwork: func(ctx context.Context, tor *tor.Tor, wait bool) error {
|
|
return nil
|
|
},
|
|
testTorGetInfo: func(ctrl *control.Conn, keys ...string) ([]*control.KeyVal, error) {
|
|
return nil, nil
|
|
},
|
|
})
|
|
if !errors.Is(err, ErrTorUnableToGetSOCKSProxyAddress) {
|
|
t.Fatal("not the error we expected", err)
|
|
}
|
|
if tun != nil {
|
|
t.Fatal("expected nil tunnel here")
|
|
}
|
|
}
|
|
|
|
func TestTorGetInfoInvalidKey(t *testing.T) {
|
|
ctx := context.Background()
|
|
tun, err := torStart(ctx, &Config{
|
|
Session: &mockable.Session{},
|
|
TunnelDir: "testdata",
|
|
testExecabsLookPath: func(name string) (string, error) {
|
|
return "/usr/local/bin/tor", nil
|
|
},
|
|
testTorStart: func(ctx context.Context, conf *tor.StartConf) (*tor.Tor, error) {
|
|
return &tor.Tor{}, nil
|
|
},
|
|
testTorEnableNetwork: func(ctx context.Context, tor *tor.Tor, wait bool) error {
|
|
return nil
|
|
},
|
|
testTorGetInfo: func(ctrl *control.Conn, keys ...string) ([]*control.KeyVal, error) {
|
|
return []*control.KeyVal{{}}, nil
|
|
},
|
|
})
|
|
if !errors.Is(err, ErrTorUnableToGetSOCKSProxyAddress) {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if tun != nil {
|
|
t.Fatal("expected nil tunnel here")
|
|
}
|
|
}
|
|
|
|
func TestTorGetInfoInvalidProxyType(t *testing.T) {
|
|
ctx := context.Background()
|
|
tun, err := torStart(ctx, &Config{
|
|
Session: &mockable.Session{},
|
|
TunnelDir: "testdata",
|
|
testExecabsLookPath: func(name string) (string, error) {
|
|
return "/usr/local/bin/tor", nil
|
|
},
|
|
testTorStart: func(ctx context.Context, conf *tor.StartConf) (*tor.Tor, error) {
|
|
return &tor.Tor{}, nil
|
|
},
|
|
testTorEnableNetwork: func(ctx context.Context, tor *tor.Tor, wait bool) error {
|
|
return nil
|
|
},
|
|
testTorGetInfo: func(ctrl *control.Conn, keys ...string) ([]*control.KeyVal, error) {
|
|
return []*control.KeyVal{{Key: "net/listeners/socks", Val: "127.0.0.1:9050"}}, nil
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if tun == nil {
|
|
t.Fatal("expected non-nil tunnel here")
|
|
}
|
|
}
|
|
|
|
func TestTorUnsupportedProxy(t *testing.T) {
|
|
ctx := context.Background()
|
|
tun, err := torStart(ctx, &Config{
|
|
Session: &mockable.Session{},
|
|
TunnelDir: "testdata",
|
|
testExecabsLookPath: func(name string) (string, error) {
|
|
return "/usr/local/bin/tor", nil
|
|
},
|
|
testTorStart: func(ctx context.Context, conf *tor.StartConf) (*tor.Tor, error) {
|
|
return &tor.Tor{}, nil
|
|
},
|
|
testTorEnableNetwork: func(ctx context.Context, tor *tor.Tor, wait bool) error {
|
|
return nil
|
|
},
|
|
testTorGetInfo: func(ctrl *control.Conn, keys ...string) ([]*control.KeyVal, error) {
|
|
return []*control.KeyVal{{Key: "net/listeners/socks", Val: "unix:/foo/bar"}}, nil
|
|
},
|
|
})
|
|
if !errors.Is(err, ErrTorReturnedUnsupportedProxy) {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if tun != nil {
|
|
t.Fatal("expected nil tunnel here")
|
|
}
|
|
}
|
|
|
|
func TestMaybeCleanupTunnelDir(t *testing.T) {
|
|
fakeTunDir := filepath.Join("testdata", "fake-tun-dir")
|
|
if err := os.RemoveAll(fakeTunDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.MkdirAll(fakeTunDir, 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fakeData := []byte("deadbeef\n")
|
|
logfile := filepath.Join(fakeTunDir, "tor.log")
|
|
if err := ioutil.WriteFile(logfile, fakeData, 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for idx := 0; idx < 3; idx++ {
|
|
filename := filepath.Join(fakeTunDir, fmt.Sprintf("torrc-%d", idx))
|
|
if err := ioutil.WriteFile(filename, fakeData, 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
filename = filepath.Join(fakeTunDir, fmt.Sprintf("control-port-%d", idx))
|
|
if err := ioutil.WriteFile(filename, fakeData, 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
filename = filepath.Join(fakeTunDir, fmt.Sprintf("antani-%d", idx))
|
|
if err := ioutil.WriteFile(filename, fakeData, 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
files, err := filepath.Glob(filepath.Join(fakeTunDir, "*"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(files) != 10 {
|
|
t.Fatal("unexpected number of files")
|
|
}
|
|
maybeCleanupTunnelDir(fakeTunDir, logfile)
|
|
files, err = filepath.Glob(filepath.Join(fakeTunDir, "*"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(files) != 3 {
|
|
t.Fatal("unexpected number of files")
|
|
}
|
|
expectPrefix := filepath.Join(fakeTunDir, "antani-")
|
|
for _, file := range files {
|
|
if !strings.HasPrefix(file, expectPrefix) {
|
|
t.Fatal("unexpected file name: ", file)
|
|
}
|
|
}
|
|
}
|