375 lines
12 KiB
Go
375 lines
12 KiB
Go
package oonimkall
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
|
|
"github.com/ooni/probe-cli/v3/internal/engine"
|
|
"github.com/ooni/probe-cli/v3/internal/model"
|
|
)
|
|
|
|
//
|
|
// Task Model
|
|
//
|
|
// The oonimkall package allows you to run OONI network
|
|
// experiments as "tasks". This file defines all the
|
|
// underlying model entailed by running such tasks.
|
|
//
|
|
// Logging
|
|
//
|
|
// This section of the file defines the types and the
|
|
// interfaces required to implement logging.
|
|
//
|
|
// The rest of the codebase will use a generic model.Logger
|
|
// as a logger. This is a pretty fundamental interface in
|
|
// ooni/probe-cli and so it's not defined in this file.
|
|
//
|
|
|
|
const taskABIVersion = 1
|
|
|
|
// Running tasks emit logs using different log levels. We
|
|
// define log levels with the usual semantics.
|
|
//
|
|
// The logger used by a task _may_ be configured to not
|
|
// emit log events that are less severe than a given
|
|
// severity.
|
|
//
|
|
// We use the following definitions both for defining the
|
|
// log level of a log and for configuring the maximum
|
|
// acceptable log level emitted by a logger.
|
|
const (
|
|
logLevelDebug2 = "DEBUG2"
|
|
logLevelDebug = "DEBUG"
|
|
logLevelInfo = "INFO"
|
|
logLevelErr = "ERR"
|
|
logLevelWarning = "WARNING"
|
|
)
|
|
|
|
//
|
|
// Emitting Events
|
|
//
|
|
// While it is running, a task emits events. This section
|
|
// of the file defines the types needed to emit events.
|
|
//
|
|
|
|
// type of emitted events.
|
|
const (
|
|
eventTypeFailureIPLookup = "failure.ip_lookup"
|
|
eventTypeFailureASNLookup = "failure.asn_lookup"
|
|
eventTypeFailureCCLookup = "failure.cc_lookup"
|
|
eventTypeFailureMeasurement = "failure.measurement"
|
|
eventTypeFailureMeasurementSubmission = "failure.measurement_submission"
|
|
eventTypeFailureReportCreate = "failure.report_create"
|
|
eventTypeFailureResolverLookup = "failure.resolver_lookup"
|
|
eventTypeFailureStartup = "failure.startup"
|
|
eventTypeLog = "log"
|
|
eventTypeMeasurement = "measurement"
|
|
eventTypeStatusEnd = "status.end"
|
|
eventTypeStatusGeoIPLookup = "status.geoip_lookup"
|
|
eventTypeStatusMeasurementDone = "status.measurement_done"
|
|
eventTypeStatusMeasurementStart = "status.measurement_start"
|
|
eventTypeStatusMeasurementSubmission = "status.measurement_submission"
|
|
eventTypeStatusProgress = "status.progress"
|
|
eventTypeStatusQueued = "status.queued"
|
|
eventTypeStatusReportCreate = "status.report_create"
|
|
eventTypeStatusResolverLookup = "status.resolver_lookup"
|
|
eventTypeStatusStarted = "status.started"
|
|
)
|
|
|
|
// taskEmitter is anything that allows us to
|
|
// emit events while running a task.
|
|
//
|
|
// Note that a task emitter _may_ be configured
|
|
// to ignore _some_ events though.
|
|
type taskEmitter interface {
|
|
// Emit emits the event (unless the emitter is
|
|
// configured to ignore this event key).
|
|
Emit(key string, value interface{})
|
|
}
|
|
|
|
// taskEmitterCloser is a closeable taskEmitter.
|
|
type taskEmitterCloser interface {
|
|
taskEmitter
|
|
io.Closer
|
|
}
|
|
|
|
type eventEmpty struct{}
|
|
|
|
// eventFailure contains information on a failure.
|
|
type eventFailure struct {
|
|
Failure string `json:"failure"`
|
|
}
|
|
|
|
// eventLog is an event containing a log message.
|
|
type eventLog struct {
|
|
LogLevel string `json:"log_level"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
type eventMeasurementGeneric struct {
|
|
Failure string `json:"failure,omitempty"`
|
|
Idx int64 `json:"idx"`
|
|
Input string `json:"input"`
|
|
JSONStr string `json:"json_str,omitempty"`
|
|
}
|
|
|
|
type eventStatusEnd struct {
|
|
DownloadedKB float64 `json:"downloaded_kb"`
|
|
Failure string `json:"failure"`
|
|
UploadedKB float64 `json:"uploaded_kb"`
|
|
}
|
|
|
|
type eventStatusGeoIPLookup struct {
|
|
ProbeASN string `json:"probe_asn"`
|
|
ProbeCC string `json:"probe_cc"`
|
|
ProbeIP string `json:"probe_ip"`
|
|
ProbeNetworkName string `json:"probe_network_name"`
|
|
}
|
|
|
|
// eventStatusProgress reports progress information.
|
|
type eventStatusProgress struct {
|
|
Message string `json:"message"`
|
|
Percentage float64 `json:"percentage"`
|
|
}
|
|
|
|
type eventStatusReportGeneric struct {
|
|
ReportID string `json:"report_id"`
|
|
}
|
|
|
|
type eventStatusResolverLookup struct {
|
|
ResolverASN string `json:"resolver_asn"`
|
|
ResolverIP string `json:"resolver_ip"`
|
|
ResolverNetworkName string `json:"resolver_network_name"`
|
|
}
|
|
|
|
// event is an event emitted by a task. This structure extends the event
|
|
// described by MK v0.10.9 FFI API (https://git.io/Jv4Rv).
|
|
type event struct {
|
|
Key string `json:"key"`
|
|
Value interface{} `json:"value"`
|
|
}
|
|
|
|
//
|
|
// OONI Session
|
|
//
|
|
// For performing several operations, including running
|
|
// experiments, we need to create an OONI session.
|
|
//
|
|
// This section of the file defines the interface between
|
|
// our oonimkall API and the real session.
|
|
//
|
|
// The abstraction representing a OONI session is taskSession.
|
|
//
|
|
|
|
// taskKVStoreFSBuilder constructs a KVStore with
|
|
// filesystem backing for running tests.
|
|
type taskKVStoreFSBuilder interface {
|
|
// NewFS creates a new KVStore using the filesystem.
|
|
NewFS(path string) (model.KeyValueStore, error)
|
|
}
|
|
|
|
// taskSessionBuilder constructs a new Session.
|
|
type taskSessionBuilder interface {
|
|
// NewSession creates a new taskSession.
|
|
NewSession(ctx context.Context,
|
|
config engine.SessionConfig) (taskSession, error)
|
|
}
|
|
|
|
// taskSession abstracts a OONI session.
|
|
type taskSession interface {
|
|
// A session can be closed.
|
|
io.Closer
|
|
|
|
// NewExperimentBuilderByName creates the builder for constructing
|
|
// a new experiment given the experiment's name.
|
|
NewExperimentBuilderByName(name string) (taskExperimentBuilder, error)
|
|
|
|
// MaybeLookupBackendsContext lookups the OONI backend unless
|
|
// this operation has already been performed.
|
|
MaybeLookupBackendsContext(ctx context.Context) error
|
|
|
|
// MaybeLookupLocationContext lookups the probe location unless
|
|
// this operation has already been performed.
|
|
MaybeLookupLocationContext(ctx context.Context) error
|
|
|
|
// ProbeIP must be called after MaybeLookupLocationContext
|
|
// and returns the resolved probe IP.
|
|
ProbeIP() string
|
|
|
|
// ProbeASNString must be called after MaybeLookupLocationContext
|
|
// and returns the resolved probe ASN as a string.
|
|
ProbeASNString() string
|
|
|
|
// ProbeCC must be called after MaybeLookupLocationContext
|
|
// and returns the resolved probe country code.
|
|
ProbeCC() string
|
|
|
|
// ProbeNetworkName must be called after MaybeLookupLocationContext
|
|
// and returns the resolved probe country code.
|
|
ProbeNetworkName() string
|
|
|
|
// ResolverANSString must be called after MaybeLookupLocationContext
|
|
// and returns the resolved resolver's ASN as a string.
|
|
ResolverASNString() string
|
|
|
|
// ResolverIP must be called after MaybeLookupLocationContext
|
|
// and returns the resolved resolver's IP.
|
|
ResolverIP() string
|
|
|
|
// ResolverNetworkName must be called after MaybeLookupLocationContext
|
|
// and returns the resolved resolver's network name.
|
|
ResolverNetworkName() string
|
|
}
|
|
|
|
// taskExperimentBuilder builds a taskExperiment.
|
|
type taskExperimentBuilder interface {
|
|
// SetCallbacks sets the experiment callbacks.
|
|
SetCallbacks(callbacks model.ExperimentCallbacks)
|
|
|
|
// InputPolicy returns the experiment's input policy.
|
|
InputPolicy() model.InputPolicy
|
|
|
|
// NewExperiment creates the new experiment.
|
|
NewExperimentInstance() taskExperiment
|
|
|
|
// Interruptible returns whether this experiment is interruptible.
|
|
Interruptible() bool
|
|
}
|
|
|
|
// taskExperiment is a runnable experiment.
|
|
type taskExperiment interface {
|
|
// KibiBytesReceived returns the KiB received by the experiment.
|
|
KibiBytesReceived() float64
|
|
|
|
// KibiBytesSent returns the KiB sent by the experiment.
|
|
KibiBytesSent() float64
|
|
|
|
// OpenReportContext opens a new report.
|
|
OpenReportContext(ctx context.Context) error
|
|
|
|
// ReportID must be called after a successful OpenReportContext
|
|
// and returns the report ID for this measurement.
|
|
ReportID() string
|
|
|
|
// MeasureWithContext runs the measurement.
|
|
MeasureWithContext(ctx context.Context, input string) (
|
|
measurement *model.Measurement, err error)
|
|
|
|
// SubmitAndUpdateMeasurementContext submits the measurement
|
|
// and updates its report ID on success.
|
|
SubmitAndUpdateMeasurementContext(
|
|
ctx context.Context, measurement *model.Measurement) error
|
|
}
|
|
|
|
//
|
|
// Task Running
|
|
//
|
|
// This section contains the interfaces allowing us
|
|
// to run a task until completion.
|
|
//
|
|
|
|
// taskRunner runs a task until completion.
|
|
type taskRunner interface {
|
|
// Run runs until completion.
|
|
Run(ctx context.Context)
|
|
}
|
|
|
|
//
|
|
// Task Settings
|
|
//
|
|
// This section defines the settings used by a task.
|
|
//
|
|
|
|
// Settings contains settings for a task. This structure derives from
|
|
// the one described by MK v0.10.9 FFI API (https://git.io/Jv4Rv), yet
|
|
// since 2020-12-03 we're not backwards compatible anymore.
|
|
type settings struct {
|
|
// Annotations contains the annotations to be added
|
|
// to every measurements performed by the task.
|
|
Annotations map[string]string `json:"annotations,omitempty"`
|
|
|
|
// AssetsDir is the directory where to store assets. This
|
|
// field is an extension of MK's specification. If
|
|
// this field is empty, the task won't start.
|
|
AssetsDir string `json:"assets_dir"`
|
|
|
|
// DisabledEvents contains disabled events. See
|
|
// https://git.io/Jv4Rv for the events names.
|
|
//
|
|
// This setting is currently ignored. We noticed the
|
|
// code was ignoring it on 2021-12-01.
|
|
DisabledEvents []string `json:"disabled_events,omitempty"`
|
|
|
|
// Inputs contains the inputs. The task will fail if it
|
|
// requires input and you provide no input.
|
|
Inputs []string `json:"inputs,omitempty"`
|
|
|
|
// LogLevel contains the logs level. See https://git.io/Jv4Rv
|
|
// for the names of the available log levels.
|
|
LogLevel string `json:"log_level,omitempty"`
|
|
|
|
// Name contains the task name. By https://git.io/Jv4Rv the
|
|
// names are in camel case, e.g. `Ndt`.
|
|
Name string `json:"name"`
|
|
|
|
// Options contains the task options.
|
|
Options settingsOptions `json:"options"`
|
|
|
|
// Proxy allows you to optionally force a specific proxy
|
|
// rather than using no proxy (the default).
|
|
//
|
|
// Use `psiphon:///` to force using Psiphon with the
|
|
// embedded configuration file. Not all builds have
|
|
// an embedded configuration file, but OONI builds have
|
|
// such a file, so they can use this functionality.
|
|
//
|
|
// Use `socks5://10.0.0.1:9050/` to connect to a SOCKS5
|
|
// proxy running on 10.0.0.1:9050. This could be, for
|
|
// example, a suitably configured `tor` instance.
|
|
Proxy string
|
|
|
|
// StateDir is the directory where to store persistent data. This
|
|
// field is an extension of MK's specification. If
|
|
// this field is empty, the task won't start.
|
|
StateDir string `json:"state_dir"`
|
|
|
|
// TempDir is the temporary directory. This field is an extension of MK's
|
|
// specification. If this field is empty, we will pick the tempdir that
|
|
// ioutil.TempDir uses by default, which may not work on mobile. According
|
|
// to our experiments as of 2020-06-10, leaving the TempDir empty works
|
|
// for iOS and does not work for Android.
|
|
TempDir string `json:"temp_dir"`
|
|
|
|
// TunnelDir is the directory where to store persistent state
|
|
// related to circumvention tunnels. This directory is required
|
|
// only if you want to use the tunnels. Added since 3.10.0.
|
|
TunnelDir string `json:"tunnel_dir"`
|
|
|
|
// Version indicates the version of this structure.
|
|
Version int64 `json:"version"`
|
|
}
|
|
|
|
// settingsOptions contains the settings options
|
|
type settingsOptions struct {
|
|
// MaxRuntime is the maximum runtime expressed in seconds. A negative
|
|
// value for this field disables the maximum runtime. Using
|
|
// a zero value will also mean disabled. This is not the
|
|
// original behaviour of Measurement Kit, which used to run
|
|
// for zero time in such case.
|
|
MaxRuntime float64 `json:"max_runtime,omitempty"`
|
|
|
|
// NoCollector indicates whether to use a collector
|
|
NoCollector bool `json:"no_collector,omitempty"`
|
|
|
|
// ProbeServicesBaseURL contains the probe services base URL.
|
|
ProbeServicesBaseURL string `json:"probe_services_base_url,omitempty"`
|
|
|
|
// SoftwareName is the software name. If this option is not
|
|
// present, then the library startup will fail.
|
|
SoftwareName string `json:"software_name,omitempty"`
|
|
|
|
// SoftwareVersion is the software version. If this option is not
|
|
// present, then the library startup will fail.
|
|
SoftwareVersion string `json:"software_version,omitempty"`
|
|
}
|