2021-03-29 19:03:53 +02:00
|
|
|
package main
|
2021-03-29 18:46:26 +02:00
|
|
|
|
2022-07-08 14:20:49 +02:00
|
|
|
//
|
|
|
|
// Core implementation
|
|
|
|
//
|
|
|
|
// TODO(bassosimone): we should eventually merge this file and main.go. We still
|
|
|
|
// have this file becaused we used to have ./internal/libminiooni.
|
|
|
|
//
|
|
|
|
|
2021-02-02 12:05:47 +01:00
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/url"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/apex/log"
|
2021-02-03 11:21:10 +01:00
|
|
|
"github.com/ooni/probe-cli/v3/internal/engine"
|
refactor: flatten and separate (#353)
* refactor(atomicx): move outside the engine package
After merging probe-engine into probe-cli, my impression is that we have
too much unnecessary nesting of packages in this repository.
The idea of this commit and of a bunch of following commits will instead
be to reduce the nesting and simplify the structure.
While there, improve the documentation.
* fix: always use the atomicx package
For consistency, never use sync/atomic and always use ./internal/atomicx
so we can just grep and make sure we're not risking to crash if we make
a subtle mistake on a 32 bit platform.
While there, mention in the contributing guidelines that we want to
always prefer the ./internal/atomicx package over sync/atomic.
* fix(atomicx): remove unnecessary constructor
We don't need a constructor here. The default constructed `&Int64{}`
instance is already usable and the constructor does not add anything to
what we are doing, rather it just creates extra confusion.
* cleanup(atomicx): we are not using Float64
Because atomicx.Float64 is unused, we can safely zap it.
* cleanup(atomicx): simplify impl and improve tests
We can simplify the implementation by using defer and by letting
the Load() method call Add(0).
We can improve tests by making many goroutines updated the
atomic int64 value concurrently.
* refactor(fsx): can live in the ./internal pkg
Let us reduce the amount of nesting. While there, ensure that the
package only exports the bare minimum, and improve the documentation
of the tests, to ease reading the code.
* refactor: move runtimex to ./internal
* refactor: move shellx into the ./internal package
While there, remove unnecessary dependency between packages.
While there, specify in the contributing guidelines that
one should use x/sys/execabs instead of os/exec.
* refactor: move ooapi into the ./internal pkg
* refactor(humanize): move to ./internal and better docs
* refactor: move platform to ./internal
* refactor(randx): move to ./internal
* refactor(multierror): move into the ./internal pkg
* refactor(kvstore): all kvstores in ./internal
Rather than having part of the kvstore inside ./internal/engine/kvstore
and part in ./internal/engine/kvstore.go, let us put every piece of code
that is kvstore related into the ./internal/kvstore package.
* fix(kvstore): always return ErrNoSuchKey on Get() error
It should help to use the kvstore everywhere removing all the
copies that are lingering around the tree.
* sessionresolver: make KVStore mandatory
Simplifies implementation. While there, use the ./internal/kvstore
package rather than having our private implementation.
* fix(ooapi): use the ./internal/kvstore package
* fix(platform): better documentation
2021-06-04 10:34:18 +02:00
|
|
|
"github.com/ooni/probe-cli/v3/internal/humanize"
|
|
|
|
"github.com/ooni/probe-cli/v3/internal/kvstore"
|
2022-05-25 10:19:03 +02:00
|
|
|
"github.com/ooni/probe-cli/v3/internal/legacy/assetsdir"
|
2022-01-03 13:53:23 +01:00
|
|
|
"github.com/ooni/probe-cli/v3/internal/model"
|
2022-07-08 14:20:49 +02:00
|
|
|
"github.com/ooni/probe-cli/v3/internal/oonirun"
|
|
|
|
"github.com/ooni/probe-cli/v3/internal/runtimex"
|
2021-02-04 11:00:27 +01:00
|
|
|
"github.com/ooni/probe-cli/v3/internal/version"
|
2021-02-02 12:05:47 +01:00
|
|
|
"github.com/pborman/getopt/v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Options contains the options you can set from the CLI.
|
|
|
|
type Options struct {
|
|
|
|
Annotations []string
|
|
|
|
ExtraOptions []string
|
|
|
|
HomeDir string
|
|
|
|
Inputs []string
|
|
|
|
InputFilePaths []string
|
2021-03-29 20:38:23 +02:00
|
|
|
MaxRuntime int64
|
2021-02-02 12:05:47 +01:00
|
|
|
NoJSON bool
|
|
|
|
NoCollector bool
|
|
|
|
ProbeServicesURL string
|
|
|
|
Proxy string
|
|
|
|
Random bool
|
2022-07-08 17:04:31 +02:00
|
|
|
RepeatEvery int64
|
2021-02-02 12:05:47 +01:00
|
|
|
ReportFile string
|
|
|
|
TorArgs []string
|
|
|
|
TorBinary string
|
|
|
|
Tunnel string
|
|
|
|
Verbose bool
|
2021-03-08 18:31:42 +01:00
|
|
|
Version bool
|
2021-02-02 12:05:47 +01:00
|
|
|
Yes bool
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
softwareName = "miniooni"
|
|
|
|
softwareVersion = version.Version
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
globalOptions Options
|
|
|
|
startTime = time.Now()
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
getopt.FlagLong(
|
|
|
|
&globalOptions.Annotations, "annotation", 'A', "Add annotaton", "KEY=VALUE",
|
|
|
|
)
|
|
|
|
getopt.FlagLong(
|
|
|
|
&globalOptions.ExtraOptions, "option", 'O',
|
|
|
|
"Pass an option to the experiment", "KEY=VALUE",
|
|
|
|
)
|
|
|
|
getopt.FlagLong(
|
|
|
|
&globalOptions.InputFilePaths, "input-file", 'f',
|
|
|
|
"Path to input file to supply test-dependent input. File must contain one input per line.", "PATH",
|
|
|
|
)
|
|
|
|
getopt.FlagLong(
|
|
|
|
&globalOptions.HomeDir, "home", 0,
|
|
|
|
"Force specific home directory", "PATH",
|
|
|
|
)
|
|
|
|
getopt.FlagLong(
|
|
|
|
&globalOptions.Inputs, "input", 'i',
|
|
|
|
"Add test-dependent input to the test input", "INPUT",
|
|
|
|
)
|
2021-03-29 20:38:23 +02:00
|
|
|
getopt.FlagLong(
|
|
|
|
&globalOptions.MaxRuntime, "max-runtime", 0,
|
|
|
|
"Maximum runtime in seconds when looping over a list of inputs (zero means infinite)", "N",
|
|
|
|
)
|
2021-02-02 12:05:47 +01:00
|
|
|
getopt.FlagLong(
|
|
|
|
&globalOptions.NoJSON, "no-json", 'N', "Disable writing to disk",
|
|
|
|
)
|
|
|
|
getopt.FlagLong(
|
|
|
|
&globalOptions.NoCollector, "no-collector", 'n', "Don't use a collector",
|
|
|
|
)
|
|
|
|
getopt.FlagLong(
|
|
|
|
&globalOptions.ProbeServicesURL, "probe-services", 0,
|
|
|
|
"Set the URL of the probe-services instance you want to use", "URL",
|
|
|
|
)
|
|
|
|
getopt.FlagLong(
|
|
|
|
&globalOptions.Proxy, "proxy", 0, "Set the proxy URL", "URL",
|
|
|
|
)
|
|
|
|
getopt.FlagLong(
|
|
|
|
&globalOptions.Random, "random", 0, "Randomize inputs",
|
|
|
|
)
|
2022-07-08 17:04:31 +02:00
|
|
|
getopt.FlagLong(
|
|
|
|
&globalOptions.RepeatEvery, "repeat-every", 0,
|
|
|
|
"Repeat the measurement every INTERVAL number of seconds", "INTERVAL",
|
|
|
|
)
|
2021-02-02 12:05:47 +01:00
|
|
|
getopt.FlagLong(
|
|
|
|
&globalOptions.ReportFile, "reportfile", 'o',
|
|
|
|
"Set the report file path", "PATH",
|
|
|
|
)
|
|
|
|
getopt.FlagLong(
|
|
|
|
&globalOptions.TorArgs, "tor-args", 0,
|
|
|
|
"Extra args for tor binary (may be specified multiple times)",
|
|
|
|
)
|
|
|
|
getopt.FlagLong(
|
|
|
|
&globalOptions.TorBinary, "tor-binary", 0,
|
|
|
|
"Specify path to a specific tor binary",
|
|
|
|
)
|
|
|
|
getopt.FlagLong(
|
|
|
|
&globalOptions.Tunnel, "tunnel", 0,
|
|
|
|
"Name of the tunnel to use (one of `tor`, `psiphon`)",
|
|
|
|
)
|
|
|
|
getopt.FlagLong(
|
|
|
|
&globalOptions.Verbose, "verbose", 'v', "Increase verbosity",
|
|
|
|
)
|
2021-03-08 18:31:42 +01:00
|
|
|
getopt.FlagLong(
|
|
|
|
&globalOptions.Version, "version", 0, "Print version and exit",
|
|
|
|
)
|
2021-02-02 12:05:47 +01:00
|
|
|
getopt.FlagLong(
|
2022-07-08 14:20:49 +02:00
|
|
|
&globalOptions.Yes, "yes", 'y',
|
|
|
|
"Assume yes as the answer to all questions",
|
2021-02-02 12:05:47 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Main is the main function of miniooni. This function parses the command line
|
|
|
|
// options and uses a global state. Use MainWithConfiguration if you want to avoid
|
|
|
|
// using any global state and relying on command line options.
|
|
|
|
//
|
|
|
|
// This function will panic in case of a fatal error. It is up to you that
|
|
|
|
// integrate this function to either handle the panic of ignore it.
|
|
|
|
func Main() {
|
|
|
|
getopt.Parse()
|
2021-03-08 18:31:42 +01:00
|
|
|
if globalOptions.Version {
|
|
|
|
fmt.Printf("%s\n", version.Version)
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
2022-07-08 14:20:49 +02:00
|
|
|
runtimex.PanicIfFalse(len(getopt.Args()) == 1, "Missing experiment name")
|
|
|
|
runtimex.PanicOnError(engine.CheckEmbeddedPsiphonConfig(), "Invalid embedded psiphon config")
|
2021-02-02 12:05:47 +01:00
|
|
|
MainWithConfiguration(getopt.Arg(0), globalOptions)
|
|
|
|
}
|
|
|
|
|
|
|
|
func split(s string) (string, string, error) {
|
|
|
|
v := strings.SplitN(s, "=", 2)
|
|
|
|
if len(v) != 2 {
|
|
|
|
return "", "", errors.New("invalid key-value pair")
|
|
|
|
}
|
|
|
|
return v[0], v[1], nil
|
|
|
|
}
|
|
|
|
|
2022-07-08 11:51:59 +02:00
|
|
|
func mustMakeMapString(input []string) (output map[string]string) {
|
2021-02-02 12:05:47 +01:00
|
|
|
output = make(map[string]string)
|
|
|
|
for _, opt := range input {
|
|
|
|
key, value, err := split(opt)
|
2022-07-08 14:20:49 +02:00
|
|
|
runtimex.PanicOnError(err, "cannot split key-value pair")
|
2021-02-02 12:05:47 +01:00
|
|
|
output[key] = value
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-07-08 11:51:59 +02:00
|
|
|
func mustMakeMapAny(input []string) (output map[string]any) {
|
|
|
|
output = make(map[string]any)
|
|
|
|
for _, opt := range input {
|
|
|
|
key, value, err := split(opt)
|
2022-07-08 14:20:49 +02:00
|
|
|
runtimex.PanicOnError(err, "cannot split key-value pair")
|
2022-07-08 11:51:59 +02:00
|
|
|
output[key] = value
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-02-02 12:05:47 +01:00
|
|
|
func mustParseURL(URL string) *url.URL {
|
|
|
|
rv, err := url.Parse(URL)
|
2022-07-08 14:20:49 +02:00
|
|
|
runtimex.PanicOnError(err, "cannot parse URL")
|
2021-02-02 12:05:47 +01:00
|
|
|
return rv
|
|
|
|
}
|
|
|
|
|
|
|
|
type logHandler struct {
|
|
|
|
io.Writer
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *logHandler) HandleLog(e *log.Entry) (err error) {
|
|
|
|
s := fmt.Sprintf("[%14.6f] <%s> %s", time.Since(startTime).Seconds(), e.Level, e.Message)
|
|
|
|
if len(e.Fields) > 0 {
|
|
|
|
s += fmt.Sprintf(": %+v", e.Fields)
|
|
|
|
}
|
|
|
|
s += "\n"
|
|
|
|
_, err = h.Writer.Write([]byte(s))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// See https://gist.github.com/miguelmota/f30a04a6d64bd52d7ab59ea8d95e54da
|
|
|
|
func gethomedir(optionsHome string) string {
|
|
|
|
if optionsHome != "" {
|
|
|
|
return optionsHome
|
|
|
|
}
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
|
|
|
|
if home == "" {
|
|
|
|
home = os.Getenv("USERPROFILE")
|
|
|
|
}
|
|
|
|
return home
|
|
|
|
}
|
|
|
|
if runtime.GOOS == "linux" {
|
|
|
|
home := os.Getenv("XDG_CONFIG_HOME")
|
|
|
|
if home != "" {
|
|
|
|
return home
|
|
|
|
}
|
|
|
|
// fallthrough
|
|
|
|
}
|
|
|
|
return os.Getenv("HOME")
|
|
|
|
}
|
|
|
|
|
|
|
|
const riskOfRunningOONI = `
|
|
|
|
Do you consent to OONI Probe data collection?
|
|
|
|
|
|
|
|
OONI Probe collects evidence of internet censorship and measures
|
|
|
|
network performance:
|
2022-07-08 11:51:59 +02:00
|
|
|
|
2021-02-02 12:05:47 +01:00
|
|
|
- OONI Probe will likely test objectionable sites and services;
|
2022-07-08 11:51:59 +02:00
|
|
|
|
2021-02-02 12:05:47 +01:00
|
|
|
- Anyone monitoring your internet activity (such as a government
|
|
|
|
or Internet provider) may be able to tell that you are using OONI Probe;
|
2022-07-08 11:51:59 +02:00
|
|
|
|
2021-02-02 12:05:47 +01:00
|
|
|
- The network data you collect will be published automatically
|
|
|
|
unless you use miniooni's -n command line flag.
|
2022-07-08 11:51:59 +02:00
|
|
|
|
2021-02-02 12:05:47 +01:00
|
|
|
To learn more, see https://ooni.org/about/risks/.
|
|
|
|
|
|
|
|
If you're onboard, re-run the same command and add the --yes flag, to
|
|
|
|
indicate that you understand the risks. This will create an empty file
|
|
|
|
named 'consent' in $HOME/.miniooni, meaning that we know you opted in
|
|
|
|
and we will not ask you this question again.
|
|
|
|
|
|
|
|
`
|
|
|
|
|
|
|
|
func canOpen(filepath string) bool {
|
|
|
|
stat, err := os.Stat(filepath)
|
|
|
|
return err == nil && stat.Mode().IsRegular()
|
|
|
|
}
|
|
|
|
|
|
|
|
func maybeWriteConsentFile(yes bool, filepath string) (err error) {
|
|
|
|
if yes {
|
2021-06-15 14:01:45 +02:00
|
|
|
err = os.WriteFile(filepath, []byte("\n"), 0644)
|
2021-02-02 12:05:47 +01:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-05 15:28:13 +02:00
|
|
|
// tunnelAndProxy is the text printed when the user specifies
|
|
|
|
// both the --tunnel and the --proxy options
|
|
|
|
const tunnelAndProxy = `USAGE ERROR: The --tunnel option and the --proxy
|
|
|
|
option cannot be specified at the same time. The --tunnel option is actually
|
|
|
|
just syntactic sugar for --proxy. Setting --tunnel=psiphon is currently the
|
|
|
|
equivalent of setting --proxy=psiphon:///. This MAY change in a future version
|
|
|
|
of miniooni, when we will allow a tunnel to use a proxy.
|
|
|
|
`
|
|
|
|
|
2021-02-02 12:05:47 +01:00
|
|
|
// MainWithConfiguration is the miniooni main with a specific configuration
|
|
|
|
// represented by the experiment name and the current options.
|
|
|
|
//
|
|
|
|
// This function will panic in case of a fatal error. It is up to you that
|
|
|
|
// integrate this function to either handle the panic of ignore it.
|
|
|
|
func MainWithConfiguration(experimentName string, currentOptions Options) {
|
2022-07-08 14:20:49 +02:00
|
|
|
runtimex.PanicIfTrue(currentOptions.Proxy != "" && currentOptions.Tunnel != "",
|
2021-04-05 15:28:13 +02:00
|
|
|
tunnelAndProxy)
|
|
|
|
if currentOptions.Tunnel != "" {
|
|
|
|
currentOptions.Proxy = fmt.Sprintf("%s:///", currentOptions.Tunnel)
|
|
|
|
}
|
2021-03-29 20:38:23 +02:00
|
|
|
|
2021-02-02 12:05:47 +01:00
|
|
|
logger := &log.Logger{Level: log.InfoLevel, Handler: &logHandler{Writer: os.Stderr}}
|
|
|
|
if currentOptions.Verbose {
|
|
|
|
logger.Level = log.DebugLevel
|
|
|
|
}
|
|
|
|
if currentOptions.ReportFile == "" {
|
|
|
|
currentOptions.ReportFile = "report.jsonl"
|
|
|
|
}
|
|
|
|
log.Log = logger
|
2022-07-08 17:04:31 +02:00
|
|
|
for {
|
|
|
|
mainSingleIteration(logger, experimentName, currentOptions)
|
|
|
|
if currentOptions.RepeatEvery <= 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
log.Infof("waiting %ds before repeating the measurement", currentOptions.RepeatEvery)
|
|
|
|
log.Info("use Ctrl-C to interrupt miniooni")
|
|
|
|
time.Sleep(time.Duration(currentOptions.RepeatEvery) * time.Second)
|
|
|
|
}
|
|
|
|
}
|
2021-02-02 12:05:47 +01:00
|
|
|
|
2022-07-08 17:04:31 +02:00
|
|
|
// mainSingleIteration runs a single iteration. There may be multiple iterations
|
|
|
|
// when the user specifies the --repeat-every command line flag.
|
|
|
|
func mainSingleIteration(logger model.Logger, experimentName string, currentOptions Options) {
|
2022-07-08 14:20:49 +02:00
|
|
|
extraOptions := mustMakeMapAny(currentOptions.ExtraOptions)
|
|
|
|
annotations := mustMakeMapString(currentOptions.Annotations)
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
2021-02-12 08:17:16 +01:00
|
|
|
//Mon Jan 2 15:04:05 -0700 MST 2006
|
|
|
|
log.Infof("Current time: %s", time.Now().Format("2006-01-02 15:04:05 MST"))
|
|
|
|
|
2021-02-02 12:05:47 +01:00
|
|
|
homeDir := gethomedir(currentOptions.HomeDir)
|
2022-07-08 14:20:49 +02:00
|
|
|
runtimex.PanicIfFalse(homeDir != "", "home directory is empty")
|
2021-02-02 12:05:47 +01:00
|
|
|
miniooniDir := path.Join(homeDir, ".miniooni")
|
2021-06-08 19:40:17 +02:00
|
|
|
err := os.MkdirAll(miniooniDir, 0700)
|
2022-07-08 14:20:49 +02:00
|
|
|
runtimex.PanicOnError(err, "cannot create $HOME/.miniooni directory")
|
2021-04-01 16:57:31 +02:00
|
|
|
|
|
|
|
// We cleanup the assets files used by versions of ooniprobe
|
|
|
|
// older than v3.9.0, where we started embedding the assets
|
|
|
|
// into the binary and use that directly. This cleanup doesn't
|
|
|
|
// remove the whole directory but only known files inside it
|
|
|
|
// and then the directory itself, if empty. We explicitly discard
|
|
|
|
// the return value as it does not matter to us here.
|
2021-02-02 12:05:47 +01:00
|
|
|
assetsDir := path.Join(miniooniDir, "assets")
|
2021-04-01 16:57:31 +02:00
|
|
|
_, _ = assetsdir.Cleanup(assetsDir)
|
|
|
|
|
2021-02-02 12:05:47 +01:00
|
|
|
log.Debugf("miniooni state directory: %s", miniooniDir)
|
|
|
|
|
|
|
|
consentFile := path.Join(miniooniDir, "informed")
|
2022-07-08 14:20:49 +02:00
|
|
|
runtimex.PanicOnError(maybeWriteConsentFile(currentOptions.Yes, consentFile),
|
2021-02-02 12:05:47 +01:00
|
|
|
"cannot write informed consent file")
|
2022-07-08 14:20:49 +02:00
|
|
|
runtimex.PanicIfFalse(canOpen(consentFile), riskOfRunningOONI)
|
2021-02-02 12:05:47 +01:00
|
|
|
log.Info("miniooni home directory: $HOME/.miniooni")
|
|
|
|
|
|
|
|
var proxyURL *url.URL
|
|
|
|
if currentOptions.Proxy != "" {
|
|
|
|
proxyURL = mustParseURL(currentOptions.Proxy)
|
|
|
|
}
|
|
|
|
|
|
|
|
kvstore2dir := filepath.Join(miniooniDir, "kvstore2")
|
refactor: flatten and separate (#353)
* refactor(atomicx): move outside the engine package
After merging probe-engine into probe-cli, my impression is that we have
too much unnecessary nesting of packages in this repository.
The idea of this commit and of a bunch of following commits will instead
be to reduce the nesting and simplify the structure.
While there, improve the documentation.
* fix: always use the atomicx package
For consistency, never use sync/atomic and always use ./internal/atomicx
so we can just grep and make sure we're not risking to crash if we make
a subtle mistake on a 32 bit platform.
While there, mention in the contributing guidelines that we want to
always prefer the ./internal/atomicx package over sync/atomic.
* fix(atomicx): remove unnecessary constructor
We don't need a constructor here. The default constructed `&Int64{}`
instance is already usable and the constructor does not add anything to
what we are doing, rather it just creates extra confusion.
* cleanup(atomicx): we are not using Float64
Because atomicx.Float64 is unused, we can safely zap it.
* cleanup(atomicx): simplify impl and improve tests
We can simplify the implementation by using defer and by letting
the Load() method call Add(0).
We can improve tests by making many goroutines updated the
atomic int64 value concurrently.
* refactor(fsx): can live in the ./internal pkg
Let us reduce the amount of nesting. While there, ensure that the
package only exports the bare minimum, and improve the documentation
of the tests, to ease reading the code.
* refactor: move runtimex to ./internal
* refactor: move shellx into the ./internal package
While there, remove unnecessary dependency between packages.
While there, specify in the contributing guidelines that
one should use x/sys/execabs instead of os/exec.
* refactor: move ooapi into the ./internal pkg
* refactor(humanize): move to ./internal and better docs
* refactor: move platform to ./internal
* refactor(randx): move to ./internal
* refactor(multierror): move into the ./internal pkg
* refactor(kvstore): all kvstores in ./internal
Rather than having part of the kvstore inside ./internal/engine/kvstore
and part in ./internal/engine/kvstore.go, let us put every piece of code
that is kvstore related into the ./internal/kvstore package.
* fix(kvstore): always return ErrNoSuchKey on Get() error
It should help to use the kvstore everywhere removing all the
copies that are lingering around the tree.
* sessionresolver: make KVStore mandatory
Simplifies implementation. While there, use the ./internal/kvstore
package rather than having our private implementation.
* fix(ooapi): use the ./internal/kvstore package
* fix(platform): better documentation
2021-06-04 10:34:18 +02:00
|
|
|
kvstore, err := kvstore.NewFS(kvstore2dir)
|
2022-07-08 14:20:49 +02:00
|
|
|
runtimex.PanicOnError(err, "cannot create kvstore2 directory")
|
2021-02-02 12:05:47 +01:00
|
|
|
|
2021-04-05 11:27:41 +02:00
|
|
|
tunnelDir := filepath.Join(miniooniDir, "tunnel")
|
|
|
|
err = os.MkdirAll(tunnelDir, 0700)
|
2022-07-08 14:20:49 +02:00
|
|
|
runtimex.PanicOnError(err, "cannot create tunnelDir")
|
2021-04-05 11:27:41 +02:00
|
|
|
|
2021-02-02 12:05:47 +01:00
|
|
|
config := engine.SessionConfig{
|
|
|
|
KVStore: kvstore,
|
|
|
|
Logger: logger,
|
|
|
|
ProxyURL: proxyURL,
|
|
|
|
SoftwareName: softwareName,
|
|
|
|
SoftwareVersion: softwareVersion,
|
|
|
|
TorArgs: currentOptions.TorArgs,
|
|
|
|
TorBinary: currentOptions.TorBinary,
|
2021-04-05 11:27:41 +02:00
|
|
|
TunnelDir: tunnelDir,
|
2021-02-02 12:05:47 +01:00
|
|
|
}
|
|
|
|
if currentOptions.ProbeServicesURL != "" {
|
2022-01-03 13:53:23 +01:00
|
|
|
config.AvailableProbeServices = []model.OOAPIService{{
|
2021-02-02 12:05:47 +01:00
|
|
|
Address: currentOptions.ProbeServicesURL,
|
|
|
|
Type: "https",
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
2021-04-05 15:28:13 +02:00
|
|
|
sess, err := engine.NewSession(ctx, config)
|
2022-07-08 14:20:49 +02:00
|
|
|
runtimex.PanicOnError(err, "cannot create measurement session")
|
2021-02-02 12:05:47 +01:00
|
|
|
defer func() {
|
|
|
|
sess.Close()
|
|
|
|
log.Infof("whole session: recv %s, sent %s",
|
refactor: flatten and separate (#353)
* refactor(atomicx): move outside the engine package
After merging probe-engine into probe-cli, my impression is that we have
too much unnecessary nesting of packages in this repository.
The idea of this commit and of a bunch of following commits will instead
be to reduce the nesting and simplify the structure.
While there, improve the documentation.
* fix: always use the atomicx package
For consistency, never use sync/atomic and always use ./internal/atomicx
so we can just grep and make sure we're not risking to crash if we make
a subtle mistake on a 32 bit platform.
While there, mention in the contributing guidelines that we want to
always prefer the ./internal/atomicx package over sync/atomic.
* fix(atomicx): remove unnecessary constructor
We don't need a constructor here. The default constructed `&Int64{}`
instance is already usable and the constructor does not add anything to
what we are doing, rather it just creates extra confusion.
* cleanup(atomicx): we are not using Float64
Because atomicx.Float64 is unused, we can safely zap it.
* cleanup(atomicx): simplify impl and improve tests
We can simplify the implementation by using defer and by letting
the Load() method call Add(0).
We can improve tests by making many goroutines updated the
atomic int64 value concurrently.
* refactor(fsx): can live in the ./internal pkg
Let us reduce the amount of nesting. While there, ensure that the
package only exports the bare minimum, and improve the documentation
of the tests, to ease reading the code.
* refactor: move runtimex to ./internal
* refactor: move shellx into the ./internal package
While there, remove unnecessary dependency between packages.
While there, specify in the contributing guidelines that
one should use x/sys/execabs instead of os/exec.
* refactor: move ooapi into the ./internal pkg
* refactor(humanize): move to ./internal and better docs
* refactor: move platform to ./internal
* refactor(randx): move to ./internal
* refactor(multierror): move into the ./internal pkg
* refactor(kvstore): all kvstores in ./internal
Rather than having part of the kvstore inside ./internal/engine/kvstore
and part in ./internal/engine/kvstore.go, let us put every piece of code
that is kvstore related into the ./internal/kvstore package.
* fix(kvstore): always return ErrNoSuchKey on Get() error
It should help to use the kvstore everywhere removing all the
copies that are lingering around the tree.
* sessionresolver: make KVStore mandatory
Simplifies implementation. While there, use the ./internal/kvstore
package rather than having our private implementation.
* fix(ooapi): use the ./internal/kvstore package
* fix(platform): better documentation
2021-06-04 10:34:18 +02:00
|
|
|
humanize.SI(sess.KibiBytesReceived()*1024, "byte"),
|
|
|
|
humanize.SI(sess.KibiBytesSent()*1024, "byte"),
|
2021-02-02 12:05:47 +01:00
|
|
|
)
|
|
|
|
}()
|
|
|
|
log.Debugf("miniooni temporary directory: %s", sess.TempDir())
|
|
|
|
|
|
|
|
log.Info("Looking up OONI backends; please be patient...")
|
|
|
|
err = sess.MaybeLookupBackends()
|
2022-07-08 14:20:49 +02:00
|
|
|
runtimex.PanicOnError(err, "cannot lookup OONI backends")
|
2021-02-02 12:05:47 +01:00
|
|
|
log.Info("Looking up your location; please be patient...")
|
|
|
|
err = sess.MaybeLookupLocation()
|
2022-07-08 14:20:49 +02:00
|
|
|
runtimex.PanicOnError(err, "cannot lookup your location")
|
2021-02-02 12:05:47 +01:00
|
|
|
log.Debugf("- IP: %s", sess.ProbeIP())
|
|
|
|
log.Infof("- country: %s", sess.ProbeCC())
|
|
|
|
log.Infof("- network: %s (%s)", sess.ProbeNetworkName(), sess.ProbeASNString())
|
|
|
|
log.Infof("- resolver's IP: %s", sess.ResolverIP())
|
|
|
|
log.Infof("- resolver's network: %s (%s)", sess.ResolverNetworkName(),
|
|
|
|
sess.ResolverASNString())
|
|
|
|
|
2022-07-08 15:17:52 +02:00
|
|
|
// We handle the oonirun experiment name specially. The user must specify
|
|
|
|
// `miniooni -i {OONIRunURL} oonirun` to run a OONI Run URL (v1 or v2).
|
|
|
|
if experimentName == "oonirun" {
|
|
|
|
ooniRunMain(ctx, sess, currentOptions, annotations)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise just run OONI experiments as we normally do.
|
2022-07-08 14:20:49 +02:00
|
|
|
desc := &oonirun.Experiment{
|
|
|
|
Annotations: annotations,
|
|
|
|
ExtraOptions: extraOptions,
|
|
|
|
Inputs: currentOptions.Inputs,
|
|
|
|
InputFilePaths: currentOptions.InputFilePaths,
|
|
|
|
MaxRuntime: currentOptions.MaxRuntime,
|
|
|
|
Name: experimentName,
|
|
|
|
NoCollector: currentOptions.NoCollector,
|
|
|
|
NoJSON: currentOptions.NoJSON,
|
|
|
|
Random: currentOptions.Random,
|
|
|
|
ReportFile: currentOptions.ReportFile,
|
2021-12-03 15:30:56 +01:00
|
|
|
Session: sess,
|
2021-03-29 20:00:50 +02:00
|
|
|
}
|
2022-07-08 14:20:49 +02:00
|
|
|
err = desc.Run(ctx)
|
|
|
|
runtimex.PanicOnError(err, "cannot run experiment")
|
2021-02-02 12:05:47 +01:00
|
|
|
}
|
2022-07-08 15:17:52 +02:00
|
|
|
|
|
|
|
// ooniRunMain runs the experiments described by the given OONI Run URLs. This
|
|
|
|
// function works with both v1 and v2 OONI Run URLs.
|
|
|
|
func ooniRunMain(ctx context.Context,
|
|
|
|
sess *engine.Session, currentOptions Options, annotations map[string]string) {
|
|
|
|
runtimex.PanicIfTrue(
|
|
|
|
len(currentOptions.Inputs) <= 0,
|
|
|
|
"in oonirun mode you need to specify at least one URL using `-i URL`",
|
|
|
|
)
|
|
|
|
runtimex.PanicIfTrue(
|
|
|
|
len(currentOptions.InputFilePaths) > 0,
|
|
|
|
"in oonirun mode you cannot specify any `-f FILE` file",
|
|
|
|
)
|
|
|
|
logger := sess.Logger()
|
|
|
|
cfg := &oonirun.LinkConfig{
|
|
|
|
AcceptChanges: currentOptions.Yes,
|
|
|
|
Annotations: annotations,
|
|
|
|
KVStore: sess.KeyValueStore(),
|
|
|
|
MaxRuntime: currentOptions.MaxRuntime,
|
|
|
|
NoCollector: currentOptions.NoCollector,
|
|
|
|
NoJSON: currentOptions.NoJSON,
|
|
|
|
Random: currentOptions.Random,
|
|
|
|
ReportFile: currentOptions.ReportFile,
|
|
|
|
Session: sess,
|
|
|
|
}
|
|
|
|
for _, URL := range currentOptions.Inputs {
|
|
|
|
r := oonirun.NewLinkRunner(cfg, URL)
|
|
|
|
if err := r.Run(ctx); err != nil {
|
2022-07-08 16:53:59 +02:00
|
|
|
if errors.Is(err, oonirun.ErrNeedToAcceptChanges) {
|
|
|
|
logger.Warnf("oonirun: to accept these changes, rerun adding `-y` to the command line")
|
|
|
|
logger.Warnf("oonirun: we'll show this error every time the upstream link changes")
|
|
|
|
panic("oonirun: need to accept changes using `-y`")
|
|
|
|
}
|
2022-07-08 15:17:52 +02:00
|
|
|
logger.Warnf("oonirun: running link failed: %s", err.Error())
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|