Skip to content

Commit

Permalink
[backport] feat: add echcheck to the experimental suite
Browse files Browse the repository at this point in the history
This diff backports #1217 to the release/3.19 branch.

- [x] I have read the [contribution
guidelines](https://github.com/ooni/probe-cli/blob/master/CONTRIBUTING.md)
- [x] reference issue for this pull request:
ooni/probe#1453
ooni/probe#2547
- [x] if you changed anything related to how experiments work and you
need to reflect these changes in the ooni/spec repository, please link
to the related ooni/spec pull request:
https://github.com/kelmenhorst/spec/tree/echcheck-spec
- [x] if you changed code inside an experiment, make sure you bump its
version number

This diff does two things:

1. Add `echcheck` to the experimental nettest suite. We do not provide
input which causes the experiment to use the default URL
`https://crypto.cloudflare.com/cdn-cgi/trace` (URL
[proposed](ooni/probe#1453 (comment))
by @eighthave). With this single input configuration we can collect the
first experimental ECH measurements.

2. Add netem tests to `echcheck`, while keeping a "real-internet" test
to connect to an ECH-enabled server.

---------

Co-authored-by: Simone Basso <[email protected]>
  • Loading branch information
kelmenhorst and bassosimone committed Oct 11, 2023
1 parent e5f950b commit a8f4fc2
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 55 deletions.
14 changes: 14 additions & 0 deletions cmd/ooniprobe/internal/nettests/echcheck.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package nettests

// ECHCheck nettest implementation.
type ECHCheck struct{}

// Run starts the nettest.
func (n ECHCheck) Run(ctl *Controller) error {
builder, err := ctl.Session.NewExperimentBuilder("echcheck")
if err != nil {
return err
}
// providing empty input causes the experiment to use the default URL
return ctl.Run(builder, []string{})
}
1 change: 1 addition & 0 deletions cmd/ooniprobe/internal/nettests/groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ var All = map[string]Group{
Label: "Experimental Nettests",
Nettests: []Nettest{
DNSCheck{},
ECHCheck{},
STUNReachability{},
TorSf{},
VanillaTor{},
Expand Down
2 changes: 1 addition & 1 deletion internal/experiment/echcheck/handshake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestHandshake(t *testing.T) {
t.Fatal(err)
}

result := handshakeWithEch(ctx, conn, time.Now(), parsed.Host, "example.org")
result := handshakeWithEch(ctx, conn, time.Now(), parsed.Host, "crypto.cloudflare.com")
if result == nil {
t.Fatal("expected result")
}
Expand Down
8 changes: 4 additions & 4 deletions internal/experiment/echcheck/measure.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import (
)

const (
testName = "echcheck"
testVersion = "0.1.1"
defaultDomain = "https://example.org"
testName = "echcheck"
testVersion = "0.1.2"
defaultURL = "https://crypto.cloudflare.com/cdn-cgi/trace"
)

var (
Expand Down Expand Up @@ -54,7 +54,7 @@ func (m *Measurer) Run(
args *model.ExperimentArgs,
) error {
if args.Measurement.Input == "" {
args.Measurement.Input = defaultDomain
args.Measurement.Input = defaultURL
}
parsed, err := url.Parse(string(args.Measurement.Input))
if err != nil {
Expand Down
133 changes: 83 additions & 50 deletions internal/experiment/echcheck/measure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,94 +4,127 @@ import (
"context"
"testing"

"github.com/apex/log"
"github.com/ooni/probe-cli/v3/internal/legacy/mockable"
"github.com/ooni/probe-cli/v3/internal/mocks"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/netemx"
)

func TestNewExperimentMeasurer(t *testing.T) {
measurer := NewExperimentMeasurer(Config{})
if measurer.ExperimentName() != "echcheck" {
t.Fatal("unexpected name")
}
if measurer.ExperimentVersion() != "0.1.1" {
if measurer.ExperimentVersion() != "0.1.2" {
t.Fatal("unexpected version")
}
}

func TestMeasurerMeasureWithInvalidInput(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel() // immediately cancel the context
sess := &mockable.Session{MockableLogger: log.Log}
callbacks := model.NewPrinterCallbacks(sess.Logger())
measurer := NewExperimentMeasurer(Config{})
measurement := &model.Measurement{
Input: "http://example.org",
}
args := &model.ExperimentArgs{
Callbacks: callbacks,
Measurement: measurement,
Session: sess,
}
err := measurer.Run(
ctx,
args,
)
if err == nil {
t.Fatal("expected an error here")
// qaenv creates a [netemx.QAEnv] with a single crypto.cloudflare.com test server and a DoH server.
func qaenv() *netemx.QAEnv {
cfg := []*netemx.ScenarioDomainAddresses{
{
Domains: []string{"crypto.cloudflare.com"},
Addresses: []string{"130.192.91.7"},
Role: netemx.ScenarioRoleWebServer,
WebServerFactory: netemx.ExampleWebPageHandlerFactory(),
},
{
Domains: []string{"mozilla.cloudflare-dns.com"},
Addresses: []string{"130.192.91.13"},
Role: netemx.ScenarioRolePublicDNS,
},
}
return netemx.MustNewScenario(cfg)
}

func TestMeasurerMeasureWithInvalidInput2(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel() // immediately cancel the context
sess := &mockable.Session{MockableLogger: log.Log}
callbacks := model.NewPrinterCallbacks(sess.Logger())
func TestMeasurerMeasureWithCancelledContext(t *testing.T) {
// create QAEnv
env := qaenv()
defer env.Close()

env.Do(func() {
ctx, cancel := context.WithCancel(context.Background())
cancel() // immediately cancel the context

// create measurer
measurer := NewExperimentMeasurer(Config{})
args := &model.ExperimentArgs{
Callbacks: model.NewPrinterCallbacks(model.DiscardLogger),
Measurement: &model.Measurement{},
Session: &mocks.Session{MockLogger: func() model.Logger { return model.DiscardLogger }},
}

// run measurement
err := measurer.Run(ctx, args)
if err == nil {
t.Fatal("expected an error here")
}
if err.Error() != "interrupted" {
t.Fatal("unexpected error type")
}
})

}

func TestMeasurerMeasureWithInvalidInput(t *testing.T) {
// create QAEnv
env := qaenv()
defer env.Close()

// create measurer
measurer := NewExperimentMeasurer(Config{})
measurement := &model.Measurement{
// leading space to test url.Parse failure
Input: " https://example.org",
}
args := &model.ExperimentArgs{
Callbacks: callbacks,
Measurement: measurement,
Session: sess,
Callbacks: model.NewPrinterCallbacks(model.DiscardLogger),
Measurement: &model.Measurement{
// leading space to test url.Parse failure
Input: " https://crypto.cloudflare.com/cdn-cgi/trace",
},
Session: &mocks.Session{MockLogger: func() model.Logger { return model.DiscardLogger }},
}
err := measurer.Run(
ctx,
args,
)
// run measurement
err := measurer.Run(context.Background(), args)
if err == nil {
t.Fatal("expected an error here")
}
if err.Error() != "input is not an URL" {
t.Fatal("unexpected error type")
}
}

func TestMeasurementSuccess(t *testing.T) {
func TestMeasurementSuccessRealWorld(t *testing.T) {
if testing.Short() {
// this test uses the real internet so we want to skip this in short mode
t.Skip("skip test in short mode")
}

sess := &mockable.Session{MockableLogger: log.Log}
callbacks := model.NewPrinterCallbacks(sess.Logger())
// create measurer
measurer := NewExperimentMeasurer(Config{})
msrmnt := &model.Measurement{}
args := &model.ExperimentArgs{
Callbacks: callbacks,
Measurement: &model.Measurement{},
Session: sess,
Callbacks: model.NewPrinterCallbacks(model.DiscardLogger),
Measurement: msrmnt,
Session: &mocks.Session{MockLogger: func() model.Logger { return model.DiscardLogger }},
}
err := measurer.Run(
context.Background(),
args,
)

// run measurement
err := measurer.Run(context.Background(), args)
if err != nil {
t.Fatal("unexpected error: ", err)
}

// check results
summary, err := measurer.GetSummaryKeys(&model.Measurement{})
if err != nil {
t.Fatal(err)
t.Fatal("unexpected error: ", err)
}
if summary.(SummaryKeys).IsAnomaly != false {
t.Fatal("expected false")
}
tk := msrmnt.TestKeys.(TestKeys)
if tk.Control.Failure != nil {
t.Fatal("unexpected control failure:", *tk.Control.Failure)
}
if tk.Target.Failure != nil {
t.Fatal("unexpected target failure:", *tk.Target.Failure)
}
}

0 comments on commit a8f4fc2

Please sign in to comment.