2021-02-02 12:05:47 +01:00
|
|
|
// 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"
|
2022-01-03 13:53:23 +01:00
|
|
|
"github.com/ooni/probe-cli/v3/internal/model"
|
2021-02-02 12:05:47 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
testName = "psiphon"
|
2022-01-07 13:17:20 +01:00
|
|
|
testVersion = "0.6.0"
|
2021-02-02 12:05:47 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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 {
|
2021-11-15 12:43:19 +01:00
|
|
|
const maxruntime = 300
|
2021-02-02 12:05:47 +01:00
|
|
|
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)
|
|
|
|
}
|
2022-01-07 13:17:20 +01:00
|
|
|
tk, _ := g.Get(ctx) // ignore error since we have the testkeys and want to submit them
|
2021-02-02 12:05:47 +01:00
|
|
|
cancel()
|
|
|
|
wg.Wait()
|
|
|
|
measurement.TestKeys = &TestKeys{
|
|
|
|
TestKeys: tk,
|
|
|
|
MaxRuntime: maxruntime,
|
|
|
|
}
|
2022-01-07 13:17:20 +01:00
|
|
|
return nil
|
2021-02-02 12:05:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewExperimentMeasurer creates a new ExperimentMeasurer.
|
|
|
|
func NewExperimentMeasurer(config Config) model.ExperimentMeasurer {
|
|
|
|
return &Measurer{Config: config}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SummaryKeys contains summary keys for this experiment.
|
|
|
|
//
|
2022-05-09 09:33:18 +02:00
|
|
|
// Note that this structure is part of the ABI contract with ooniprobe
|
2021-02-02 12:05:47 +01:00
|
|
|
// 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
|
|
|
|
}
|