ooni-probe-cli/internal/engine/experiment/tlstool/tlstool.go
Simone Basso 58adb68b2c
refactor: move tracex outside of engine/netx (#782)
* refactor: move tracex outside of engine/netx

Consistently with https://github.com/ooni/probe/issues/2121 and
https://github.com/ooni/probe/issues/2115, we can now move tracex
outside of engine/netx. The main reason why this makes sense now
is that the package is now changed significantly from the one
that we imported from ooni/probe-engine.

We have improved its implementation, which had not been touched
significantly for quite some time, and converted it to unit
testing. I will document tomorrow some extra work I'd like to
do with this package but likely could not do $soon.

* go fmt

* regen tutorials
2022-06-02 00:50:55 +02:00

178 lines
5.0 KiB
Go

// Package tlstool contains a TLS tool that we are currently using
// for running quick and dirty experiments. This tool will change
// without notice and may be removed without notice.
//
// Caveats
//
// In particular, this experiment MAY panic when passed incorrect
// input. This is acceptable because this is not production ready code.
package tlstool
import (
"context"
"crypto/tls"
"fmt"
"net"
"time"
"github.com/ooni/probe-cli/v3/internal/engine/experiment/tlstool/internal"
"github.com/ooni/probe-cli/v3/internal/engine/netx"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/runtimex"
"github.com/ooni/probe-cli/v3/internal/tracex"
)
const (
testName = "tlstool"
testVersion = "0.1.0"
)
// Config contains the experiment configuration.
type Config struct {
Delay int64 `ooni:"Milliseconds to wait between writes"`
SNI string `ooni:"Force using the specified SNI"`
}
// TestKeys contains the experiment results.
type TestKeys struct {
Experiment map[string]*ExperimentKeys `json:"experiment"`
}
// ExperimentKeys contains the specific experiment results.
type ExperimentKeys struct {
Failure *string `json:"failure"`
}
// Measurer performs the measurement.
type Measurer struct {
config Config
}
// ExperimentName implements ExperimentMeasurer.ExperiExperimentName.
func (m Measurer) ExperimentName() string {
return testName
}
// ExperimentVersion implements ExperimentMeasurer.ExperimentVersion.
func (m Measurer) ExperimentVersion() string {
return testVersion
}
type method struct {
name string
newDialer func(internal.DialerConfig) internal.Dialer
}
var allMethods = []method{{
name: "vanilla",
newDialer: internal.NewVanillaDialer,
}, {
name: "snisplit",
newDialer: internal.NewSNISplitterDialer,
}, {
name: "random",
newDialer: internal.NewRandomSplitterDialer,
}, {
name: "thrice",
newDialer: internal.NewThriceSplitterDialer,
}}
// Run implements ExperimentMeasurer.Run.
func (m Measurer) Run(
ctx context.Context,
sess model.ExperimentSession,
measurement *model.Measurement,
callbacks model.ExperimentCallbacks,
) error {
// TODO(bassosimone): wondering whether this experiment should
// actually be merged with sniblocking instead?
tk := new(TestKeys)
tk.Experiment = make(map[string]*ExperimentKeys)
measurement.TestKeys = tk
address := string(measurement.Input)
for idx, meth := range allMethods {
// TODO(bassosimone): here we actually want to use urlgetter
// if possible and collect standard test keys.
err := m.run(ctx, runConfig{
address: address,
logger: sess.Logger(),
newDialer: meth.newDialer,
})
percent := float64(idx) / float64(len(allMethods))
callbacks.OnProgress(percent, fmt.Sprintf("%s: %+v", meth.name, err))
tk.Experiment[meth.name] = &ExperimentKeys{
Failure: tracex.NewFailure(err),
}
}
return nil // return nil so we always submit the measurement
}
func (m Measurer) newDialer(logger model.Logger) model.Dialer {
// TODO(bassosimone): this is a resolver that should hopefully work
// in many places. Maybe allow to configure it?
resolver, err := netx.NewDNSClientWithOverrides(netx.Config{Logger: logger},
"https://cloudflare.com/dns-query", "dns.cloudflare.com", "", "")
runtimex.PanicOnError(err, "cannot initialize resolver")
return netx.NewDialer(netx.Config{FullResolver: resolver, Logger: logger})
}
type runConfig struct {
address string
logger model.Logger
newDialer func(internal.DialerConfig) internal.Dialer
}
func (m Measurer) run(ctx context.Context, config runConfig) error {
dialer := config.newDialer(internal.DialerConfig{
Dialer: m.newDialer(config.logger),
Delay: time.Duration(m.config.Delay) * time.Millisecond,
SNI: m.pattern(config.address),
})
tdialer := netx.NewTLSDialer(netx.Config{
Dialer: dialer,
Logger: config.logger,
TLSConfig: m.tlsConfig(),
})
conn, err := tdialer.DialTLSContext(ctx, "tcp", config.address)
if err != nil {
return err
}
conn.Close()
return nil
}
func (m Measurer) tlsConfig() *tls.Config {
if m.config.SNI != "" {
return &tls.Config{ServerName: m.config.SNI}
}
return nil
}
func (m Measurer) pattern(address string) string {
if m.config.SNI != "" {
return m.config.SNI
}
addr, _, err := net.SplitHostPort(address)
// TODO(bassosimone): replace this panic with proper error checking.
runtimex.PanicOnError(err, "cannot split address")
return addr
}
// NewExperimentMeasurer creates a new ExperimentMeasurer.
func NewExperimentMeasurer(config Config) model.ExperimentMeasurer {
return Measurer{config: config}
}
// SummaryKeys contains summary keys for this experiment.
//
// Note that this structure is part of the ABI contract with ooniprobe
// therefore we should be careful when changing it.
type SummaryKeys struct {
IsAnomaly bool `json:"-"`
}
// GetSummaryKeys implements model.ExperimentMeasurer.GetSummaryKeys.
func (m Measurer) GetSummaryKeys(measurement *model.Measurement) (interface{}, error) {
return SummaryKeys{IsAnomaly: false}, nil
}