d57c78bc71
This is how I did it: 1. `git clone https://github.com/ooni/probe-engine internal/engine` 2. ``` (cd internal/engine && git describe --tags) v0.23.0 ``` 3. `nvim go.mod` (merging `go.mod` with `internal/engine/go.mod` 4. `rm -rf internal/.git internal/engine/go.{mod,sum}` 5. `git add internal/engine` 6. `find . -type f -name \*.go -exec sed -i 's@/ooni/probe-engine@/ooni/probe-cli/v3/internal/engine@g' {} \;` 7. `go build ./...` (passes) 8. `go test -race ./...` (temporary failure on RiseupVPN) 9. `go mod tidy` 10. this commit message Once this piece of work is done, we can build a new version of `ooniprobe` that is using `internal/engine` directly. We need to do more work to ensure all the other functionality in `probe-engine` (e.g. making mobile packages) are still WAI. Part of https://github.com/ooni/probe/issues/1335
149 lines
3.8 KiB
Go
149 lines
3.8 KiB
Go
package probeservices
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
|
)
|
|
|
|
// Default returns the default probe services
|
|
func Default() []model.Service {
|
|
return []model.Service{{
|
|
Address: "https://ps1.ooni.io",
|
|
Type: "https",
|
|
}, {
|
|
Address: "https://ps2.ooni.io",
|
|
Type: "https",
|
|
}, {
|
|
Front: "dkyhjv0wpi2dk.cloudfront.net",
|
|
Type: "cloudfront",
|
|
Address: "https://dkyhjv0wpi2dk.cloudfront.net",
|
|
}}
|
|
}
|
|
|
|
// SortEndpoints gives priority to https, then cloudfronted, then onion.
|
|
func SortEndpoints(in []model.Service) (out []model.Service) {
|
|
for _, entry := range in {
|
|
if entry.Type == "https" {
|
|
out = append(out, entry)
|
|
}
|
|
}
|
|
for _, entry := range in {
|
|
if entry.Type == "cloudfront" {
|
|
out = append(out, entry)
|
|
}
|
|
}
|
|
for _, entry := range in {
|
|
if entry.Type == "onion" {
|
|
out = append(out, entry)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// OnlyHTTPS returns the HTTPS endpoints only.
|
|
func OnlyHTTPS(in []model.Service) (out []model.Service) {
|
|
for _, entry := range in {
|
|
if entry.Type == "https" {
|
|
out = append(out, entry)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// OnlyFallbacks returns the fallback endpoints only.
|
|
func OnlyFallbacks(in []model.Service) (out []model.Service) {
|
|
for _, entry := range SortEndpoints(in) {
|
|
if entry.Type != "https" {
|
|
out = append(out, entry)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Candidate is a candidate probe service.
|
|
type Candidate struct {
|
|
// Duration is the time it took to access the service.
|
|
Duration time.Duration
|
|
|
|
// Err indicates whether the service works.
|
|
Err error
|
|
|
|
// Endpoint is the service endpoint.
|
|
Endpoint model.Service
|
|
|
|
// TestHelpers contains the data returned by the endpoint.
|
|
TestHelpers map[string][]model.Service
|
|
}
|
|
|
|
func (c *Candidate) try(ctx context.Context, sess Session) {
|
|
client, err := NewClient(sess, c.Endpoint)
|
|
if err != nil {
|
|
c.Err = err
|
|
return
|
|
}
|
|
start := time.Now()
|
|
testhelpers, err := client.GetTestHelpers(ctx)
|
|
c.Duration = time.Now().Sub(start)
|
|
c.Err = err
|
|
c.TestHelpers = testhelpers
|
|
sess.Logger().Debugf("probe services: %+v: %+v %s", c.Endpoint, err, c.Duration)
|
|
}
|
|
|
|
func try(ctx context.Context, sess Session, svc model.Service) *Candidate {
|
|
candidate := &Candidate{Endpoint: svc}
|
|
candidate.try(ctx, sess)
|
|
return candidate
|
|
}
|
|
|
|
// TryAll tries all the input services using the provided context and session. It
|
|
// returns a list containing information on each candidate that was tried. We will
|
|
// try all the HTTPS candidates first. So, the beginning of the list will contain
|
|
// all of them, and for each of them you will know whether it worked (by checking the
|
|
// Err field) and how fast it was (by checking the Duration field). You should pick
|
|
// the fastest one that worked. If none of them works, then TryAll will subsequently
|
|
// attempt with all the available fallbacks, and return at the first success. In
|
|
// such case, you will see a list of N failing HTTPS candidates, followed by a single
|
|
// successful fallback candidate (e.g. cloudfronted). If all candidates fail, you
|
|
// see in output a list containing all entries where Err is not nil.
|
|
func TryAll(ctx context.Context, sess Session, in []model.Service) (out []*Candidate) {
|
|
var found bool
|
|
for _, svc := range OnlyHTTPS(in) {
|
|
candidate := try(ctx, sess, svc)
|
|
out = append(out, candidate)
|
|
if candidate.Err == nil {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
for _, svc := range OnlyFallbacks(in) {
|
|
candidate := try(ctx, sess, svc)
|
|
out = append(out, candidate)
|
|
if candidate.Err == nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// SelectBest selects the best among the candidates. If there is no
|
|
// suitable candidate, then this function returns nil.
|
|
func SelectBest(candidates []*Candidate) (selected *Candidate) {
|
|
for _, e := range candidates {
|
|
if e.Err != nil {
|
|
continue
|
|
}
|
|
if selected == nil {
|
|
selected = e
|
|
continue
|
|
}
|
|
if selected.Duration > e.Duration {
|
|
selected = e
|
|
continue
|
|
}
|
|
}
|
|
return
|
|
}
|