ooni-probe-cli/internal/measurexlite/dialer.go

153 lines
4.1 KiB
Go
Raw Normal View History

package measurexlite
//
// Dialer tracing
//
import (
"context"
"log"
"math"
"net"
"strconv"
"time"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/netxlite"
"github.com/ooni/probe-cli/v3/internal/tracex"
)
// NewDialerWithoutResolver is equivalent to netxlite.NewDialerWithoutResolver
// except that it returns a model.Dialer that uses this trace.
//
// Note: unlike code in netx or measurex, this factory DOES NOT return you a
// dialer that also performs wrapping of a net.Conn in case of success. If you
// want to wrap the conn, you need to wrap it explicitly using WrapNetConn.
func (tx *Trace) NewDialerWithoutResolver(dl model.DebugLogger) model.Dialer {
return &dialerTrace{
d: tx.newDialerWithoutResolver(dl),
tx: tx,
}
}
// dialerTrace is a trace-aware model.Dialer.
type dialerTrace struct {
d model.Dialer
tx *Trace
}
var _ model.Dialer = &dialerTrace{}
// DialContext implements model.Dialer.DialContext.
func (d *dialerTrace) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
return d.d.DialContext(netxlite.ContextWithTrace(ctx, d.tx), network, address)
}
// CloseIdleConnections implements model.Dialer.CloseIdleConnections.
func (d *dialerTrace) CloseIdleConnections() {
d.d.CloseIdleConnections()
}
// OnTCPConnectDone implements model.Trace.OnTCPConnectDone.
func (tx *Trace) OnConnectDone(
started time.Time, network, domain, remoteAddr string, err error, finished time.Time) {
switch network {
case "tcp", "tcp4", "tcp6":
// insert into the tcpConnect buffer
select {
case tx.tcpConnect <- NewArchivalTCPConnectResult(
tx.Index,
started.Sub(tx.ZeroTime),
remoteAddr,
err,
finished.Sub(tx.ZeroTime),
):
default: // buffer is full
}
// insert into the networkEvent buffer
// see https://github.com/ooni/probe/issues/2254
select {
case tx.networkEvent <- NewArchivalNetworkEvent(
tx.Index,
started.Sub(tx.ZeroTime),
netxlite.ConnectOperation,
"tcp",
remoteAddr,
0,
err,
finished.Sub(tx.ZeroTime),
):
default: // buffer is full
}
default:
// ignore UDP connect attempts because they cannot fail
// in interesting ways that make sense for censorship
}
}
// NewArchivalTCPConnectResult generates a model.ArchivalTCPConnectResult
// from the available information right after connect returns.
func NewArchivalTCPConnectResult(index int64, started time.Duration, address string,
err error, finished time.Duration) *model.ArchivalTCPConnectResult {
ip, port := archivalSplitHostPort(address)
return &model.ArchivalTCPConnectResult{
IP: ip,
Port: archivalPortToString(port),
Status: model.ArchivalTCPConnectStatus{
Blocked: nil,
Failure: tracex.NewFailure(err),
Success: err == nil,
},
T0: started.Seconds(),
T: finished.Seconds(),
TransactionID: index,
}
}
// archivalSplitHostPort is like net.SplitHostPort but does not return an error. This
// function returns two empty strings in case of any failure.
func archivalSplitHostPort(endpoint string) (string, string) {
addr, port, err := net.SplitHostPort(endpoint)
if err != nil {
log.Printf("BUG: archivalSplitHostPort: invalid endpoint: %s", endpoint)
return "", ""
}
return addr, port
}
// archivalPortToString is like strconv.Atoi but does not return an error. This
// function returns a zero port number in case of any failure.
func archivalPortToString(sport string) int {
port, err := strconv.Atoi(sport)
if err != nil || port < 0 || port > math.MaxUint16 {
log.Printf("BUG: archivalStrconvAtoi: invalid port: %s", sport)
return 0
}
return port
}
// TCPConnects drains the network events buffered inside the TCPConnect channel.
func (tx *Trace) TCPConnects() (out []*model.ArchivalTCPConnectResult) {
for {
select {
case ev := <-tx.tcpConnect:
out = append(out, ev)
default:
return // done
}
}
}
// FirstTCPConnectOrNil drains the network events buffered inside the TCPConnect channel
// and returns the first TCPConnect, if any. Otherwise, it returns nil.
func (tx *Trace) FirstTCPConnectOrNil() *model.ArchivalTCPConnectResult {
ev := tx.TCPConnects()
if len(ev) < 1 {
return nil
}
return ev[0]
}