ooni-probe-cli/internal/cmd/oohelperd/tcpconnect.go
Simone Basso 4241ee4bc1
feat(oohelperd): log messages at info level (#896)
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
2022-08-28 22:26:58 +02:00

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
}
}
}