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"` }