4241ee4bc1
We're using a request-specific logger where we also print the ID of the request. This design helps to observe logs produced by concurrent requests. Part of https://github.com/ooni/probe/issues/2183 While there, fix https://github.com/ooni/probe/issues/2241
142 lines
3.7 KiB
Go
142 lines
3.7 KiB
Go
package main
|
|
|
|
//
|
|
// TCP connect (and optionally TLS handshake) measurements
|
|
//
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ooni/probe-cli/v3/internal/measurexlite"
|
|
"github.com/ooni/probe-cli/v3/internal/model"
|
|
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
|
)
|
|
|
|
// ctrlTCPResult is the result of the TCP check performed by the test helper.
|
|
type ctrlTCPResult = model.THTCPConnectResult
|
|
|
|
// ctrlTLSResult is the result of the TLS check performed by the test helper.
|
|
type ctrlTLSResult = model.THTLSHandshakeResult
|
|
|
|
// tcpResultPair contains the endpoint and the corresponding result.
|
|
type tcpResultPair struct {
|
|
// Address is the IP address we measured.
|
|
Address string
|
|
|
|
// Endpoint is the endpoint we measured.
|
|
Endpoint string
|
|
|
|
// TCP contains the TCP results.
|
|
TCP ctrlTCPResult
|
|
|
|
// TLS contains the TLS results
|
|
TLS *ctrlTLSResult
|
|
}
|
|
|
|
// tcpConfig configures the TCP connect check.
|
|
type tcpConfig struct {
|
|
// Address is the MANDATORY address to measure.
|
|
Address string
|
|
|
|
// EnableTLS OPTIONALLY enables TLS.
|
|
EnableTLS bool
|
|
|
|
// Endpoint is the MANDATORY endpoint to connect to.
|
|
Endpoint string
|
|
|
|
// Logger is the MANDATORY logger to use.
|
|
Logger model.Logger
|
|
|
|
// NewDialer is the MANDATORY factory for creating a new dialer.
|
|
NewDialer func(model.Logger) model.Dialer
|
|
|
|
// NewTSLHandshaker is the MANDATORY factory for creating a new handshaker.
|
|
NewTSLHandshaker func(model.Logger) model.TLSHandshaker
|
|
|
|
// Out is the MANDATORY where we'll post the TCP measurement results.
|
|
Out chan *tcpResultPair
|
|
|
|
// URLHostname is the MANDATORY URL.Hostname() to use.
|
|
URLHostname string
|
|
|
|
// Wg is MANDATORY and is used to sync with the parent.
|
|
Wg *sync.WaitGroup
|
|
}
|
|
|
|
// tcpDo performs the TCP check.
|
|
func tcpDo(ctx context.Context, config *tcpConfig) {
|
|
const timeout = 15 * time.Second
|
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
|
defer cancel()
|
|
defer config.Wg.Done()
|
|
out := &tcpResultPair{
|
|
Address: config.Address,
|
|
Endpoint: config.Endpoint,
|
|
TCP: model.THTCPConnectResult{},
|
|
TLS: nil, // means: not measured
|
|
}
|
|
defer func() {
|
|
config.Out <- out
|
|
}()
|
|
ol := measurexlite.NewOperationLogger(
|
|
config.Logger,
|
|
"TCPConnect %s EnableTLS=%v SNI=%s",
|
|
config.Endpoint,
|
|
config.EnableTLS,
|
|
config.URLHostname,
|
|
)
|
|
dialer := config.NewDialer(config.Logger)
|
|
defer dialer.CloseIdleConnections()
|
|
conn, err := dialer.DialContext(ctx, "tcp", config.Endpoint)
|
|
out.TCP.Failure = tcpMapFailure(newfailure(err))
|
|
out.TCP.Status = err == nil
|
|
defer measurexlite.MaybeClose(conn)
|
|
if err != nil || !config.EnableTLS {
|
|
ol.Stop(err)
|
|
return
|
|
}
|
|
tlsConfig := &tls.Config{
|
|
NextProtos: []string{"h2", "http/1.1"},
|
|
RootCAs: netxlite.NewDefaultCertPool(),
|
|
ServerName: config.URLHostname,
|
|
}
|
|
thx := config.NewTSLHandshaker(config.Logger)
|
|
tlsConn, _, err := thx.Handshake(ctx, conn, tlsConfig)
|
|
ol.Stop(err)
|
|
out.TLS = &ctrlTLSResult{
|
|
ServerName: config.URLHostname,
|
|
Status: err == nil,
|
|
Failure: newfailure(err),
|
|
}
|
|
measurexlite.MaybeClose(tlsConn)
|
|
}
|
|
|
|
// tcpMapFailure 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#L392
|
|
func tcpMapFailure(failure *string) *string {
|
|
switch failure {
|
|
case nil:
|
|
return nil
|
|
default:
|
|
switch *failure {
|
|
case netxlite.FailureGenericTimeoutError:
|
|
return failure // already using the same name
|
|
case netxlite.FailureConnectionRefused:
|
|
s := "connection_refused_error"
|
|
return &s
|
|
default:
|
|
// The definition of this error according to Twisted is
|
|
// "something went wrong when connecting". Because we are
|
|
// indeed basically just connecting here, it seems safe
|
|
// to map any other error to "connect_error" here.
|
|
s := "connect_error"
|
|
return &s
|
|
}
|
|
}
|
|
}
|