99ec7ffca9
Since https://github.com/ooni/probe-cli/pull/527, if an experiment returns an error, the corresponding measurement is not submitted since the semantics of returning an error is that something fundamental went wrong (e.g., we could not parse the input URL). This diff ensures that all experiments only return and error when something fundamental was wrong and return nil otherwise. Reference issue: https://github.com/ooni/probe/issues/1808.
134 lines
3.3 KiB
Go
134 lines
3.3 KiB
Go
// Package psiphon implements the psiphon network experiment. This
|
|
// implements, in particular, v0.2.0 of the spec.
|
|
//
|
|
// See https://github.com/ooni/spec/blob/master/nettests/ts-015-psiphon.md
|
|
package psiphon
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ooni/probe-cli/v3/internal/engine/experiment/urlgetter"
|
|
"github.com/ooni/probe-cli/v3/internal/model"
|
|
)
|
|
|
|
const (
|
|
testName = "psiphon"
|
|
testVersion = "0.6.0"
|
|
)
|
|
|
|
// Config contains the experiment's configuration.
|
|
type Config struct{}
|
|
|
|
// TestKeys contains the experiment's result.
|
|
type TestKeys struct {
|
|
urlgetter.TestKeys
|
|
MaxRuntime float64 `json:"max_runtime"`
|
|
}
|
|
|
|
// Measurer is the psiphon measurer.
|
|
type Measurer struct {
|
|
BeforeGetHook func(g urlgetter.Getter)
|
|
Config Config
|
|
}
|
|
|
|
// ExperimentName returns the experiment name
|
|
func (m *Measurer) ExperimentName() string {
|
|
return testName
|
|
}
|
|
|
|
// ExperimentVersion returns the experiment version
|
|
func (m *Measurer) ExperimentVersion() string {
|
|
return testVersion
|
|
}
|
|
|
|
func (m *Measurer) printprogress(
|
|
ctx context.Context, wg *sync.WaitGroup,
|
|
maxruntime int, callbacks model.ExperimentCallbacks,
|
|
) {
|
|
ticker := time.NewTicker(time.Second)
|
|
defer ticker.Stop()
|
|
step := 1 / float64(maxruntime)
|
|
var progress float64
|
|
defer callbacks.OnProgress(1.0, "psiphon experiment complete")
|
|
defer wg.Done()
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
progress += step
|
|
callbacks.OnProgress(progress, "psiphon experiment running")
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Run runs the measurement
|
|
func (m *Measurer) Run(
|
|
ctx context.Context, sess model.ExperimentSession,
|
|
measurement *model.Measurement, callbacks model.ExperimentCallbacks,
|
|
) error {
|
|
const maxruntime = 300
|
|
ctx, cancel := context.WithTimeout(ctx, maxruntime*time.Second)
|
|
var (
|
|
wg sync.WaitGroup
|
|
config urlgetter.Config
|
|
)
|
|
wg.Add(1)
|
|
go m.printprogress(ctx, &wg, maxruntime, callbacks)
|
|
config.Tunnel = "psiphon" // force to use psiphon tunnel
|
|
urlgetter.RegisterExtensions(measurement)
|
|
target := "https://www.google.com/humans.txt"
|
|
if measurement.Input != "" {
|
|
target = string(measurement.Input)
|
|
}
|
|
g := urlgetter.Getter{
|
|
Config: config,
|
|
Session: sess,
|
|
Target: target,
|
|
}
|
|
if m.BeforeGetHook != nil {
|
|
m.BeforeGetHook(g)
|
|
}
|
|
tk, _ := g.Get(ctx) // ignore error since we have the testkeys and want to submit them
|
|
cancel()
|
|
wg.Wait()
|
|
measurement.TestKeys = &TestKeys{
|
|
TestKeys: tk,
|
|
MaxRuntime: maxruntime,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NewExperimentMeasurer creates a new ExperimentMeasurer.
|
|
func NewExperimentMeasurer(config Config) model.ExperimentMeasurer {
|
|
return &Measurer{Config: config}
|
|
}
|
|
|
|
// SummaryKeys contains summary keys for this experiment.
|
|
//
|
|
// Note that this structure is part of the ABI contract with probe-cli
|
|
// therefore we should be careful when changing it.
|
|
type SummaryKeys struct {
|
|
BootstrapTime float64 `json:"bootstrap_time"`
|
|
Failure string `json:"failure"`
|
|
IsAnomaly bool `json:"-"`
|
|
}
|
|
|
|
// GetSummaryKeys implements model.ExperimentMeasurer.GetSummaryKeys.
|
|
func (m Measurer) GetSummaryKeys(measurement *model.Measurement) (interface{}, error) {
|
|
sk := SummaryKeys{IsAnomaly: false}
|
|
tk, ok := measurement.TestKeys.(*TestKeys)
|
|
if !ok {
|
|
return sk, errors.New("invalid test keys type")
|
|
}
|
|
if tk.Failure != nil {
|
|
sk.Failure = *tk.Failure
|
|
sk.IsAnomaly = true
|
|
}
|
|
sk.BootstrapTime = tk.BootstrapTime
|
|
return sk, nil
|
|
}
|