dfa5e708fe
This diff rewrites the tor experiment to use measurex "easy" API. To this end, we need to introduce an "easy" measurex API, which basically performs easy measurements returning two pieces of data: 1. the resulting measurement, which is already using the OONI archival data format and is always non-nil 2. a failure (i.e., the pointer to an error string), which is nil on success and points to a string on failure With this change, we should now be able to completely dispose of the original netx API, which was only used by tor. Reference issue: https://github.com/ooni/probe/issues/1688.
838 lines
20 KiB
Go
838 lines
20 KiB
Go
package tor
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/apex/log"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/ooni/probe-cli/v3/internal/engine/mockable"
|
|
"github.com/ooni/probe-cli/v3/internal/measurex"
|
|
"github.com/ooni/probe-cli/v3/internal/model"
|
|
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
|
"github.com/ooni/probe-cli/v3/internal/scrubber"
|
|
)
|
|
|
|
func TestNewExperimentMeasurer(t *testing.T) {
|
|
measurer := NewExperimentMeasurer(Config{})
|
|
if measurer.ExperimentName() != "tor" {
|
|
t.Fatal("unexpected name")
|
|
}
|
|
if measurer.ExperimentVersion() != "0.4.0" {
|
|
t.Fatal("unexpected version")
|
|
}
|
|
}
|
|
|
|
func TestMeasurerMeasureFetchTorTargetsError(t *testing.T) {
|
|
measurer := NewMeasurer(Config{})
|
|
expected := errors.New("mocked error")
|
|
measurer.fetchTorTargets = func(ctx context.Context, sess model.ExperimentSession, cc string) (map[string]model.OOAPITorTarget, error) {
|
|
return nil, expected
|
|
}
|
|
err := measurer.Run(
|
|
context.Background(),
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
new(model.Measurement),
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
}
|
|
|
|
func TestMeasurerMeasureFetchTorTargetsEmptyList(t *testing.T) {
|
|
measurer := NewMeasurer(Config{})
|
|
measurer.fetchTorTargets = func(ctx context.Context, sess model.ExperimentSession, cc string) (map[string]model.OOAPITorTarget, error) {
|
|
return nil, nil
|
|
}
|
|
measurement := new(model.Measurement)
|
|
err := measurer.Run(
|
|
context.Background(),
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
measurement,
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tk := measurement.TestKeys.(*TestKeys)
|
|
if len(tk.Targets) != 0 {
|
|
t.Fatal("expected no targets here")
|
|
}
|
|
}
|
|
|
|
func TestMeasurerMeasureGoodWithMockedOrchestra(t *testing.T) {
|
|
// This test mocks orchestra to return a nil list of targets, so the code runs
|
|
// but we don't perform any actual network actions.
|
|
measurer := NewMeasurer(Config{})
|
|
measurer.fetchTorTargets = func(ctx context.Context, sess model.ExperimentSession, cc string) (map[string]model.OOAPITorTarget, error) {
|
|
return nil, nil
|
|
}
|
|
err := measurer.Run(
|
|
context.Background(),
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
new(model.Measurement),
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestMeasurerMeasureGood(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skip test in short mode")
|
|
}
|
|
measurer := NewMeasurer(Config{})
|
|
sess := newsession()
|
|
measurement := new(model.Measurement)
|
|
err := measurer.Run(
|
|
context.Background(),
|
|
sess,
|
|
measurement,
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sk, err := measurer.GetSummaryKeys(measurement)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, ok := sk.(SummaryKeys); !ok {
|
|
t.Fatal("invalid type for summary keys")
|
|
}
|
|
}
|
|
|
|
var staticPrivateTestingTargetEndpoint = "209.148.46.65:443"
|
|
|
|
var staticPrivateTestingTarget = model.OOAPITorTarget{
|
|
Address: staticPrivateTestingTargetEndpoint,
|
|
Params: map[string][]string{
|
|
"cert": {
|
|
"ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw",
|
|
},
|
|
"iat-mode": {"0"},
|
|
},
|
|
Protocol: "obfs4",
|
|
Source: "bridgedb",
|
|
}
|
|
|
|
func TestMeasurerMeasureSanitiseOutput(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skip test in short mode")
|
|
}
|
|
measurer := NewMeasurer(Config{})
|
|
sess := newsession()
|
|
key := "xyz-xyz-xyz-theCh2ju-ahG4chei-Ai2eka0a"
|
|
sess.MockableFetchTorTargetsResult = map[string]model.OOAPITorTarget{
|
|
key: staticPrivateTestingTarget,
|
|
}
|
|
measurement := new(model.Measurement)
|
|
err := measurer.Run(
|
|
context.Background(),
|
|
sess,
|
|
measurement,
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data, err := json.Marshal(measurement)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tk := measurement.TestKeys.(*TestKeys)
|
|
entry := tk.Targets[key]
|
|
if entry.Failure != nil {
|
|
t.Fatal("measurement failed unexpectedly", *entry.Failure)
|
|
}
|
|
if !bytes.Contains(data, []byte(key)) {
|
|
t.Fatal("cannot find expected key")
|
|
}
|
|
if bytes.Contains(data, []byte(staticPrivateTestingTargetEndpoint)) {
|
|
t.Fatal("endpoint found in serialized measurement")
|
|
}
|
|
if !bytes.Contains(data, []byte("[scrubbed]")) {
|
|
t.Fatal("[scrubbed] not found in serialized measurement")
|
|
}
|
|
}
|
|
|
|
var staticTestingTargets = []model.OOAPITorTarget{
|
|
{
|
|
Address: "192.95.36.142:443",
|
|
Params: map[string][]string{
|
|
"cert": {
|
|
"qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ",
|
|
},
|
|
"iat-mode": {"1"},
|
|
},
|
|
Protocol: "obfs4",
|
|
},
|
|
{
|
|
Address: "66.111.2.131:9030",
|
|
Protocol: "dir_port",
|
|
},
|
|
{
|
|
Address: "66.111.2.131:9001",
|
|
Protocol: "or_port",
|
|
},
|
|
{
|
|
Address: "1.1.1.1:80",
|
|
Protocol: "tcp",
|
|
},
|
|
}
|
|
|
|
func TestMeasurerMeasureTargetsNoInput(t *testing.T) {
|
|
var measurement model.Measurement
|
|
measurer := new(Measurer)
|
|
measurer.measureTargets(
|
|
context.Background(),
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
&measurement,
|
|
model.NewPrinterCallbacks(log.Log),
|
|
nil,
|
|
)
|
|
if len(measurement.TestKeys.(*TestKeys).Targets) != 0 {
|
|
t.Fatal("expected no measurements here")
|
|
}
|
|
}
|
|
|
|
func TestMeasurerMeasureTargetsCanceledContext(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // so we don't actually do anything
|
|
var measurement model.Measurement
|
|
measurer := new(Measurer)
|
|
measurer.measureTargets(
|
|
ctx,
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
&measurement,
|
|
model.NewPrinterCallbacks(log.Log),
|
|
map[string]model.OOAPITorTarget{
|
|
"xx": staticTestingTargets[0],
|
|
},
|
|
)
|
|
targets := measurement.TestKeys.(*TestKeys).Targets
|
|
if len(targets) != 1 {
|
|
t.Fatal("expected single measurements here")
|
|
}
|
|
if _, found := targets["xx"]; !found {
|
|
t.Fatal("the target we expected is missing")
|
|
}
|
|
tgt := targets["xx"]
|
|
if *tgt.Failure != "interrupted" {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
}
|
|
|
|
func wrapTestingTarget(tt model.OOAPITorTarget) keytarget {
|
|
return keytarget{
|
|
key: "xx", // using an super simple key; should work anyway
|
|
target: tt,
|
|
}
|
|
}
|
|
|
|
func TestResultsCollectorMeasureSingleTargetGood(t *testing.T) {
|
|
rc := newResultsCollector(
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
new(model.Measurement),
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
rc.flexibleConnect = func(context.Context, keytarget) (*measurex.ArchivalMeasurement, *string) {
|
|
return &measurex.ArchivalMeasurement{}, nil
|
|
}
|
|
rc.measureSingleTarget(
|
|
context.Background(), wrapTestingTarget(staticTestingTargets[0]),
|
|
len(staticTestingTargets),
|
|
)
|
|
if len(rc.targetresults) != 1 {
|
|
t.Fatal("wrong number of entries")
|
|
}
|
|
// Implementation note: here we won't bother with checking that
|
|
// oonidatamodel works correctly because we already test that.
|
|
if rc.targetresults["xx"].Agent != "redirect" {
|
|
t.Fatal("agent is invalid")
|
|
}
|
|
if rc.targetresults["xx"].Failure != nil {
|
|
t.Fatal("failure is invalid")
|
|
}
|
|
if rc.targetresults["xx"].TargetAddress != staticTestingTargets[0].Address {
|
|
t.Fatal("target address is invalid")
|
|
}
|
|
if rc.targetresults["xx"].TargetProtocol != staticTestingTargets[0].Protocol {
|
|
t.Fatal("target protocol is invalid")
|
|
}
|
|
}
|
|
|
|
func TestResultsCollectorMeasureSingleTargetWithFailure(t *testing.T) {
|
|
rc := newResultsCollector(
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
new(model.Measurement),
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
rc.flexibleConnect = func(context.Context, keytarget) (*measurex.ArchivalMeasurement, *string) {
|
|
failure := "mocked error"
|
|
return &measurex.ArchivalMeasurement{}, &failure
|
|
}
|
|
rc.measureSingleTarget(
|
|
context.Background(), keytarget{
|
|
key: "xx", // using an super simple key; should work anyway
|
|
target: staticTestingTargets[0],
|
|
},
|
|
len(staticTestingTargets),
|
|
)
|
|
if len(rc.targetresults) != 1 {
|
|
t.Fatal("wrong number of entries")
|
|
}
|
|
// Implementation note: here we won't bother with checking that
|
|
// oonidatamodel works correctly because we already test that.
|
|
if rc.targetresults["xx"].Agent != "redirect" {
|
|
t.Fatal("agent is invalid")
|
|
}
|
|
if *rc.targetresults["xx"].Failure != "mocked error" {
|
|
t.Fatal("failure is invalid")
|
|
}
|
|
if rc.targetresults["xx"].TargetAddress != staticTestingTargets[0].Address {
|
|
t.Fatal("target address is invalid")
|
|
}
|
|
if rc.targetresults["xx"].TargetProtocol != staticTestingTargets[0].Protocol {
|
|
t.Fatal("target protocol is invalid")
|
|
}
|
|
}
|
|
|
|
func TestDefautFlexibleConnectDirPort(t *testing.T) {
|
|
rc := newResultsCollector(
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
new(model.Measurement),
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
tk, failure := rc.defaultFlexibleConnect(ctx, wrapTestingTarget(staticTestingTargets[1]))
|
|
if failure == nil {
|
|
t.Fatal("expected a failure here")
|
|
}
|
|
if !strings.HasSuffix(*failure, "interrupted") {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if tk.Requests == nil {
|
|
t.Fatal("expected HTTP data here")
|
|
}
|
|
}
|
|
|
|
func TestDefautFlexibleConnectOrPort(t *testing.T) {
|
|
rc := newResultsCollector(
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
new(model.Measurement),
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
tk, failure := rc.defaultFlexibleConnect(ctx, wrapTestingTarget(staticTestingTargets[2]))
|
|
if failure == nil {
|
|
t.Fatal("expected a failure here")
|
|
}
|
|
if *failure != "interrupted" {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if tk.TCPConnect == nil {
|
|
t.Fatal("expected connects data here")
|
|
}
|
|
if tk.NetworkEvents != nil {
|
|
t.Fatal("expected no network events data here")
|
|
}
|
|
}
|
|
|
|
func TestDefautFlexibleConnectOBFS4(t *testing.T) {
|
|
rc := newResultsCollector(
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
new(model.Measurement),
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
tk, failure := rc.defaultFlexibleConnect(ctx, wrapTestingTarget(staticTestingTargets[0]))
|
|
if failure == nil {
|
|
t.Fatal("expected a failure here")
|
|
}
|
|
if *failure != "interrupted" {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if tk.TCPConnect == nil {
|
|
t.Fatal("expected connects data here")
|
|
}
|
|
if tk.NetworkEvents != nil {
|
|
t.Fatal("expected no network events data here")
|
|
}
|
|
}
|
|
|
|
func TestDefautFlexibleConnectDefault(t *testing.T) {
|
|
rc := newResultsCollector(
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
new(model.Measurement),
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
tk, failure := rc.defaultFlexibleConnect(ctx, wrapTestingTarget(staticTestingTargets[3]))
|
|
if failure == nil {
|
|
t.Fatal("expected a failure here")
|
|
}
|
|
if *failure != "interrupted" {
|
|
t.Fatalf("not the error we expected: %+v", *failure)
|
|
}
|
|
if tk.TCPConnect == nil {
|
|
t.Fatalf("expected connects data here, found: %+v", tk.TCPConnect)
|
|
}
|
|
}
|
|
|
|
func TestFailureString(t *testing.T) {
|
|
if failureString(nil) != "success" {
|
|
t.Fatal("not working with nil")
|
|
}
|
|
s := "antani"
|
|
if failureString(&s) != "antani" {
|
|
t.Fatal("not working with non-nil string")
|
|
}
|
|
}
|
|
|
|
func TestSummary(t *testing.T) {
|
|
t.Run("without any piece of data", func(t *testing.T) {
|
|
tr := new(TargetResults)
|
|
tr.fillSummary()
|
|
if len(tr.Summary) != 0 {
|
|
t.Fatal("summary must be empty")
|
|
}
|
|
})
|
|
|
|
t.Run("with a TCP connect and nothing else", func(t *testing.T) {
|
|
tr := new(TargetResults)
|
|
failure := "mocked_error"
|
|
tr.TCPConnect = append(tr.TCPConnect, &measurex.ArchivalTCPConnect{
|
|
Status: &measurex.ArchivalTCPConnectStatus{
|
|
Success: true,
|
|
Failure: &failure,
|
|
},
|
|
})
|
|
tr.fillSummary()
|
|
if len(tr.Summary) != 1 {
|
|
t.Fatal("cannot find expected entry")
|
|
}
|
|
if *tr.Summary[netxlite.ConnectOperation].Failure != failure {
|
|
t.Fatal("invalid failure")
|
|
}
|
|
})
|
|
|
|
t.Run("for OBFS4", func(t *testing.T) {
|
|
tr := new(TargetResults)
|
|
tr.TCPConnect = append(tr.TCPConnect, &measurex.ArchivalTCPConnect{
|
|
Status: &measurex.ArchivalTCPConnectStatus{
|
|
Success: true,
|
|
},
|
|
})
|
|
failure := "mocked_error"
|
|
tr.TargetProtocol = "obfs4"
|
|
tr.Failure = &failure
|
|
tr.fillSummary()
|
|
if len(tr.Summary) != 2 {
|
|
t.Fatal("cannot find expected entry")
|
|
}
|
|
if tr.Summary[netxlite.ConnectOperation].Failure != nil {
|
|
t.Fatal("invalid failure")
|
|
}
|
|
if *tr.Summary["handshake"].Failure != failure {
|
|
t.Fatal("invalid failure")
|
|
}
|
|
})
|
|
|
|
t.Run("for or_port/or_port_dirauth", func(t *testing.T) {
|
|
doit := func(targetProtocol string, handshake *measurex.ArchivalQUICTLSHandshakeEvent) {
|
|
tr := new(TargetResults)
|
|
tr.TCPConnect = append(tr.TCPConnect, &measurex.ArchivalTCPConnect{
|
|
Status: &measurex.ArchivalTCPConnectStatus{
|
|
Success: true,
|
|
},
|
|
})
|
|
tr.TargetProtocol = targetProtocol
|
|
if handshake != nil {
|
|
tr.TLSHandshakes = append(tr.TLSHandshakes, handshake)
|
|
}
|
|
tr.fillSummary()
|
|
if len(tr.Summary) < 1 {
|
|
t.Fatal("cannot find expected entry")
|
|
}
|
|
if tr.Summary[netxlite.ConnectOperation].Failure != nil {
|
|
t.Fatal("invalid failure")
|
|
}
|
|
if handshake == nil {
|
|
if len(tr.Summary) != 1 {
|
|
t.Fatal("unexpected summary length")
|
|
}
|
|
return
|
|
}
|
|
if len(tr.Summary) != 2 {
|
|
t.Fatal("unexpected summary length")
|
|
}
|
|
if tr.Summary["handshake"].Failure != handshake.Failure {
|
|
t.Fatal("the failure value is unexpected")
|
|
}
|
|
}
|
|
doit("or_port_dirauth", nil)
|
|
doit("or_port", nil)
|
|
doit("or_port", &measurex.ArchivalQUICTLSHandshakeEvent{
|
|
Failure: (func() *string {
|
|
s := io.EOF.Error()
|
|
return &s
|
|
})(),
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestFillToplevelKeys(t *testing.T) {
|
|
var tr TargetResults
|
|
tr.TargetProtocol = "or_port"
|
|
tk := new(TestKeys)
|
|
tk.Targets = make(map[string]TargetResults)
|
|
tk.Targets["xxx"] = tr
|
|
tk.fillToplevelKeys()
|
|
if tk.ORPortTotal != 1 {
|
|
t.Fatal("unexpected ORPortTotal value")
|
|
}
|
|
}
|
|
|
|
func newsession() *mockable.Session {
|
|
return &mockable.Session{
|
|
MockableLogger: log.Log,
|
|
MockableHTTPClient: http.DefaultClient,
|
|
}
|
|
}
|
|
|
|
var referenceTargetResult = []byte(`{
|
|
"agent": "redirect",
|
|
"failure": null,
|
|
"network_events": [
|
|
{
|
|
"address": "85.31.186.98:443",
|
|
"conn_id": 19,
|
|
"dial_id": 21,
|
|
"failure": null,
|
|
"operation": "connect",
|
|
"proto": "tcp",
|
|
"t": 8.639313
|
|
},
|
|
{
|
|
"conn_id": 19,
|
|
"failure": null,
|
|
"num_bytes": 1915,
|
|
"operation": "write",
|
|
"proto": "tcp",
|
|
"t": 8.639686
|
|
},
|
|
{
|
|
"conn_id": 19,
|
|
"failure": null,
|
|
"num_bytes": 1440,
|
|
"operation": "read",
|
|
"proto": "tcp",
|
|
"t": 8.691708
|
|
},
|
|
{
|
|
"conn_id": 19,
|
|
"failure": null,
|
|
"num_bytes": 1440,
|
|
"operation": "read",
|
|
"proto": "tcp",
|
|
"t": 8.691912
|
|
},
|
|
{
|
|
"conn_id": 19,
|
|
"failure": null,
|
|
"num_bytes": 1383,
|
|
"operation": "read",
|
|
"proto": "tcp",
|
|
"t": 8.69234
|
|
}
|
|
],
|
|
"queries": null,
|
|
"requests": null,
|
|
"summary": {
|
|
"connect": {
|
|
"failure": null
|
|
}
|
|
},
|
|
"target_address": "85.31.186.98:443",
|
|
"target_protocol": "obfs4",
|
|
"tcp_connect": [
|
|
{
|
|
"conn_id": 19,
|
|
"dial_id": 21,
|
|
"ip": "85.31.186.98",
|
|
"port": 443,
|
|
"status": {
|
|
"failure": null,
|
|
"success": true
|
|
},
|
|
"t": 8.639313
|
|
}
|
|
],
|
|
"tls_handshakes": null
|
|
}`)
|
|
|
|
var scrubbedTargetResult = []byte(`{
|
|
"agent": "redirect",
|
|
"failure": null,
|
|
"network_events": [
|
|
{
|
|
"address": "[scrubbed]",
|
|
"conn_id": 19,
|
|
"dial_id": 21,
|
|
"failure": null,
|
|
"operation": "connect",
|
|
"proto": "tcp",
|
|
"t": 8.639313
|
|
},
|
|
{
|
|
"conn_id": 19,
|
|
"failure": null,
|
|
"num_bytes": 1915,
|
|
"operation": "write",
|
|
"proto": "tcp",
|
|
"t": 8.639686
|
|
},
|
|
{
|
|
"conn_id": 19,
|
|
"failure": null,
|
|
"num_bytes": 1440,
|
|
"operation": "read",
|
|
"proto": "tcp",
|
|
"t": 8.691708
|
|
},
|
|
{
|
|
"conn_id": 19,
|
|
"failure": null,
|
|
"num_bytes": 1440,
|
|
"operation": "read",
|
|
"proto": "tcp",
|
|
"t": 8.691912
|
|
},
|
|
{
|
|
"conn_id": 19,
|
|
"failure": null,
|
|
"num_bytes": 1383,
|
|
"operation": "read",
|
|
"proto": "tcp",
|
|
"t": 8.69234
|
|
}
|
|
],
|
|
"queries": null,
|
|
"requests": null,
|
|
"summary": {
|
|
"connect": {
|
|
"failure": null
|
|
}
|
|
},
|
|
"target_address": "[scrubbed]",
|
|
"target_protocol": "obfs4",
|
|
"tcp_connect": [
|
|
{
|
|
"conn_id": 19,
|
|
"dial_id": 21,
|
|
"ip": "[scrubbed]",
|
|
"port": 443,
|
|
"status": {
|
|
"failure": null,
|
|
"success": true
|
|
},
|
|
"t": 8.639313
|
|
}
|
|
],
|
|
"tls_handshakes": null
|
|
}`)
|
|
|
|
func TestMaybeSanitize(t *testing.T) {
|
|
var input TargetResults
|
|
if err := json.Unmarshal(referenceTargetResult, &input); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Run("nothing to do", func(t *testing.T) {
|
|
out := maybeSanitize(input, keytarget{target: model.OOAPITorTarget{Source: ""}})
|
|
diff := cmp.Diff(input, out)
|
|
if diff != "" {
|
|
t.Fatal(diff)
|
|
}
|
|
})
|
|
t.Run("scrubbing to do", func(t *testing.T) {
|
|
var expected TargetResults
|
|
if err := json.Unmarshal(scrubbedTargetResult, &expected); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out := maybeSanitize(input, keytarget{target: model.OOAPITorTarget{
|
|
Address: "85.31.186.98:443",
|
|
Source: "bridgedb",
|
|
}})
|
|
diff := cmp.Diff(expected, out)
|
|
if diff != "" {
|
|
t.Fatal(diff)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestMaybeScrubbingLogger(t *testing.T) {
|
|
var input model.Logger = log.Log
|
|
|
|
t.Run("for when we don't need to save", func(t *testing.T) {
|
|
kt := keytarget{target: model.OOAPITorTarget{
|
|
Source: "",
|
|
}}
|
|
out := maybeScrubbingLogger(input, kt)
|
|
if out != input {
|
|
t.Fatal("not the output we expected")
|
|
}
|
|
if _, ok := out.(*scrubber.Logger); ok {
|
|
t.Fatal("not the output type we expected")
|
|
}
|
|
})
|
|
|
|
t.Run("for when we need to save", func(t *testing.T) {
|
|
kt := keytarget{target: model.OOAPITorTarget{
|
|
Source: "bridgedb",
|
|
}}
|
|
out := maybeScrubbingLogger(input, kt)
|
|
if out == input {
|
|
t.Fatal("not the output value we expected")
|
|
}
|
|
if _, ok := out.(*scrubber.Logger); !ok {
|
|
t.Fatal("not the output type we expected")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSummaryKeysInvalidType(t *testing.T) {
|
|
measurement := new(model.Measurement)
|
|
m := &Measurer{}
|
|
_, err := m.GetSummaryKeys(measurement)
|
|
if err.Error() != "invalid test keys type" {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
}
|
|
|
|
func TestSummaryKeysWorksAsIntended(t *testing.T) {
|
|
tests := []struct {
|
|
tk TestKeys
|
|
isAnomaly bool
|
|
}{{
|
|
tk: TestKeys{},
|
|
isAnomaly: false,
|
|
}, {
|
|
tk: TestKeys{DirPortAccessible: 1, DirPortTotal: 3},
|
|
isAnomaly: false,
|
|
}, {
|
|
tk: TestKeys{DirPortAccessible: 0, DirPortTotal: 3},
|
|
isAnomaly: true,
|
|
}, {
|
|
tk: TestKeys{OBFS4Accessible: 1, OBFS4Total: 3},
|
|
isAnomaly: false,
|
|
}, {
|
|
tk: TestKeys{OBFS4Accessible: 0, OBFS4Total: 3},
|
|
isAnomaly: true,
|
|
}, {
|
|
tk: TestKeys{ORPortDirauthAccessible: 1, ORPortDirauthTotal: 3},
|
|
isAnomaly: false,
|
|
}, {
|
|
tk: TestKeys{ORPortDirauthAccessible: 0, ORPortDirauthTotal: 3},
|
|
isAnomaly: true,
|
|
}, {
|
|
tk: TestKeys{ORPortAccessible: 1, ORPortTotal: 3},
|
|
isAnomaly: false,
|
|
}, {
|
|
tk: TestKeys{ORPortAccessible: 0, ORPortTotal: 3},
|
|
isAnomaly: true,
|
|
}}
|
|
for idx, tt := range tests {
|
|
t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) {
|
|
m := &Measurer{}
|
|
measurement := &model.Measurement{TestKeys: &tt.tk}
|
|
got, err := m.GetSummaryKeys(measurement)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
return
|
|
}
|
|
sk := got.(SummaryKeys)
|
|
if sk.IsAnomaly != tt.isAnomaly {
|
|
t.Fatal("unexpected isAnomaly value")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTargetResultsFillSummaryDirPort(t *testing.T) {
|
|
tr := &TargetResults{
|
|
TargetProtocol: "dir_port",
|
|
TCPConnect: []*measurex.ArchivalTCPConnect{{
|
|
IP: "1.2.3.4",
|
|
Port: 443,
|
|
Status: &measurex.ArchivalTCPConnectStatus{
|
|
Failure: nil,
|
|
},
|
|
}},
|
|
}
|
|
tr.fillSummary()
|
|
if tr.DirPortCount != 1 {
|
|
t.Fatal("unexpected dirPortCount")
|
|
}
|
|
}
|
|
|
|
func TestTestKeysFillToplevelKeysCoverMissingFields(t *testing.T) {
|
|
failureString := "eof_error"
|
|
tk := &TestKeys{
|
|
Targets: map[string]TargetResults{
|
|
"foobar": {Failure: &failureString, TargetProtocol: "dir_port"},
|
|
"baz": {TargetProtocol: "dir_port"},
|
|
"jafar": {Failure: &failureString, TargetProtocol: "or_port_dirauth"},
|
|
"jasmine": {TargetProtocol: "or_port_dirauth"},
|
|
},
|
|
}
|
|
tk.fillToplevelKeys()
|
|
if tk.DirPortTotal != 2 {
|
|
t.Fatal("unexpected DirPortTotal")
|
|
}
|
|
if tk.DirPortAccessible != 1 {
|
|
t.Fatal("unexpected DirPortAccessible")
|
|
}
|
|
if tk.ORPortDirauthTotal != 2 {
|
|
t.Fatal("unexpected ORPortDirauthTotal")
|
|
}
|
|
if tk.ORPortDirauthAccessible != 1 {
|
|
t.Fatal("unexpected ORPortDirauthAccessible")
|
|
}
|
|
}
|