refactor: introduce and use InputOrStaticDefault (#632)

This commit introduces a new `InputLoader` policy by which, if no
input is provided, we use a static default input list.

We also modify the code to use this policy for dnscheck and
stunreachability, with proper input.

We also modify `miniooni` to pass the new `ExperimentName` field to
the `InputLoader` to indicate which default input list to use.

This diff is part of a set of diffs aiming at fixing
https://github.com/ooni/probe/issues/1814 and has been
extracted from https://github.com/ooni/probe-cli/pull/539.

What remains to be done, after this diff has landed is to ensure
things also work for ooniprobe and oonimkall.
This commit is contained in:
Simone Basso 2021-12-03 15:30:56 +01:00 committed by GitHub
commit 2044b78a5a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 296 additions and 6 deletions

View file

@ -6,10 +6,12 @@ import (
"errors"
"fmt"
"io/fs"
"net/url"
"github.com/apex/log"
"github.com/ooni/probe-cli/v3/internal/engine/model"
"github.com/ooni/probe-cli/v3/internal/fsx"
"github.com/ooni/probe-cli/v3/internal/stuninput"
)
// These errors are returned by the InputLoader.
@ -18,6 +20,7 @@ var (
ErrDetectedEmptyFile = errors.New("file did not contain any input")
ErrInputRequired = errors.New("no input provided")
ErrNoInputExpected = errors.New("we did not expect any input")
ErrNoStaticInput = errors.New("no static input for this experiment")
)
// InputLoaderSession is the session according to an InputLoader. We
@ -58,6 +61,12 @@ type InputLoaderLogger interface {
// input, we return it. Otherwise, we use OONI's probe services
// to gather input using the best API for the task.
//
// InputOrStaticDefault
//
// We gather input from StaticInput and SourceFiles. If there is
// input, we return it. Otherwise, we return an internal static
// list of inputs to be used with this experiment.
//
// InputStrictlyRequired
//
// We gather input from StaticInput and SourceFiles. If there is
@ -69,6 +78,10 @@ type InputLoader struct {
// will set them to a default value.
CheckInConfig *model.CheckInConfig
// ExperimentName is the name of the experiment. This field
// is only used together with the InputOrStaticDefault policy.
ExperimentName string
// InputPolicy specifies the input policy for the
// current experiment. We will not load any input if
// the policy says we should not. You MUST fill in
@ -105,6 +118,8 @@ func (il *InputLoader) Load(ctx context.Context) ([]model.URLInfo, error) {
return il.loadOrQueryBackend(ctx)
case InputStrictlyRequired:
return il.loadStrictlyRequired(ctx)
case InputOrStaticDefault:
return il.loadOrStaticDefault(ctx)
default:
return il.loadNone()
}
@ -147,6 +162,59 @@ func (il *InputLoader) loadOrQueryBackend(ctx context.Context) ([]model.URLInfo,
return il.loadRemote(ctx)
}
// TODO(https://github.com/ooni/probe/issues/1390): we need to
// implement serving DNSCheck targets from the API
var dnsCheckDefaultInput = []string{
"https://dns.google/dns-query",
"https://8.8.8.8/dns-query",
"dot://8.8.8.8:853/",
"dot://8.8.4.4:853/",
"https://8.8.4.4/dns-query",
"https://cloudflare-dns.com/dns-query",
"https://1.1.1.1/dns-query",
"https://1.0.0.1/dns-query",
"dot://1.1.1.1:853/",
"dot://1.0.0.1:853/",
"https://dns.quad9.net/dns-query",
"https://9.9.9.9/dns-query",
"dot://9.9.9.9:853/",
"dot://dns.quad9.net/",
}
var stunReachabilityDefaultInput = stuninput.AsnStunReachabilityInput()
// staticBareInputForExperiment returns the list of strings an
// experiment should use as static input. In case there is no
// static input for this experiment, we return an error.
func staticBareInputForExperiment(name string) ([]string, error) {
// Implementation note: we may be called from pkg/oonimkall
// with a non-canonical experiment name, so we need to convert
// the experiment name to be canonical before proceeding.
switch canonicalizeExperimentName(name) {
case "dnscheck":
return dnsCheckDefaultInput, nil
case "stunreachability":
return stunReachabilityDefaultInput, nil
default:
return nil, ErrNoStaticInput
}
}
// staticInputForExperiment returns the static input for the given experiment
// or an error if there's no static input for the experiment.
func staticInputForExperiment(name string) ([]model.URLInfo, error) {
return stringListToModelURLInfo(staticBareInputForExperiment(name))
}
// loadOrStaticDefault implements the InputOrStaticDefault policy.
func (il *InputLoader) loadOrStaticDefault(ctx context.Context) ([]model.URLInfo, error) {
inputs, err := il.loadLocal()
if err != nil || len(inputs) > 0 {
return inputs, err
}
return staticInputForExperiment(il.ExperimentName)
}
// loadLocal loads inputs from StaticInputs and SourceFiles.
func (il *InputLoader) loadLocal() ([]model.URLInfo, error) {
inputs := []model.URLInfo{}
@ -264,3 +332,26 @@ func (il *InputLoader) logger() InputLoaderLogger {
}
return log.Log
}
// stringListToModelURLInfo is an utility function to convert
// a list of strings containing URLs into a list of model.URLInfo
// which would have been returned by an hypothetical backend
// API serving input for a test for which we don't have an API
// yet (e.g., stunreachability and dnscheck).
func stringListToModelURLInfo(input []string, err error) ([]model.URLInfo, error) {
if err != nil {
return nil, err
}
var output []model.URLInfo
for _, URL := range input {
if _, err := url.Parse(URL); err != nil {
return nil, err
}
output = append(output, model.URLInfo{
CategoryCode: "MISC", // hard to find a category
CountryCode: "XX", // representing no country
URL: URL,
})
}
return output, nil
}