Simone Basso bbcd2e2280
refactor(netx): merge archival, trace, and the savers (#772)
This diff creates a new package under netx called tracex that
contains everything we need to perform measurements using events
tracing and postprocessing (which is the technique with which
we implement most network experiments).

The general idea here is to (1) create a unique package out of
all of these packages; (2) clean up the code a bit (improve tests,
docs, apply more recent code patterns); (3) move the resulting
code as a toplevel package inside of internal.

Once this is done, netx can be further refactored to avoid
subpackages and we can search for more code to salvage/refactor.

See https://github.com/ooni/probe/issues/2121
2022-05-31 21:53:01 +02:00

124 lines
3.4 KiB
Go

package webconnectivity
import (
"context"
"io"
"net/http"
"strings"
"sync"
"github.com/ooni/probe-cli/v3/internal/engine/experiment/webconnectivity"
"github.com/ooni/probe-cli/v3/internal/engine/netx/tracex"
"github.com/ooni/probe-cli/v3/internal/netxlite"
)
// CtrlHTTPResponse is the result of the HTTP check performed by
// the Web Connectivity test helper.
type CtrlHTTPResponse = webconnectivity.ControlHTTPRequestResult
// HTTPConfig configures the HTTP check.
type HTTPConfig struct {
Client *http.Client
Headers map[string][]string
MaxAcceptableBody int64
Out chan CtrlHTTPResponse
URL string
Wg *sync.WaitGroup
}
// HTTPDo performs the HTTP check.
func HTTPDo(ctx context.Context, config *HTTPConfig) {
defer config.Wg.Done()
req, err := http.NewRequestWithContext(ctx, "GET", config.URL, nil)
if err != nil {
config.Out <- CtrlHTTPResponse{ // fix: emit -1 like the old test helper does
BodyLength: -1,
Failure: httpMapFailure(err),
StatusCode: -1,
Headers: map[string]string{},
}
return
}
// The original test helper failed with extra headers while here
// we're implementing (for now?) a more liberal approach.
for k, vs := range config.Headers {
switch strings.ToLower(k) {
case "user-agent", "accept", "accept-language":
for _, v := range vs {
req.Header.Add(k, v)
}
}
}
resp, err := config.Client.Do(req)
if err != nil {
config.Out <- CtrlHTTPResponse{ // fix: emit -1 like old test helper does
BodyLength: -1,
Failure: httpMapFailure(err),
StatusCode: -1,
Headers: map[string]string{},
}
return
}
defer resp.Body.Close()
headers := make(map[string]string)
for k := range resp.Header {
headers[k] = resp.Header.Get(k)
}
reader := &io.LimitedReader{R: resp.Body, N: config.MaxAcceptableBody}
data, err := netxlite.ReadAllContext(ctx, reader)
config.Out <- CtrlHTTPResponse{
BodyLength: int64(len(data)),
Failure: httpMapFailure(err),
StatusCode: int64(resp.StatusCode),
Headers: headers,
Title: webconnectivity.GetTitle(string(data)),
}
}
// httpMapFailure attempts to map netxlite failures to the strings
// used by the original OONI test helper.
//
// See https://github.com/ooni/backend/blob/6ec4fda5b18/oonib/testhelpers/http_helpers.py#L361
func httpMapFailure(err error) *string {
failure := newfailure(err)
failedOperation := tracex.NewFailedOperation(err)
switch failure {
case nil:
return nil
default:
switch *failure {
case netxlite.FailureDNSNXDOMAINError,
netxlite.FailureDNSNoAnswer,
netxlite.FailureDNSNonRecoverableFailure,
netxlite.FailureDNSRefusedError,
netxlite.FailureDNSServerMisbehaving,
netxlite.FailureDNSTemporaryFailure:
// Strangely the HTTP code uses the more broad
// dns_lookup_error and does not check for
// the NXDOMAIN-equivalent-error dns_name_error
s := "dns_lookup_error"
return &s
case netxlite.FailureGenericTimeoutError:
// The old TH would return "dns_lookup_error" when
// there is a timeout error during the DNS phase of HTTP.
switch failedOperation {
case nil:
// nothing
default:
switch *failedOperation {
case netxlite.ResolveOperation:
s := "dns_lookup_error"
return &s
}
}
return failure // already using the same name
case netxlite.FailureConnectionRefused:
s := "connection_refused_error"
return &s
default:
s := "unknown_error"
return &s
}
}
}