146 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			146 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package tlsmiddlebox
 | |
| 
 | |
| //
 | |
| // Iterative network tracing
 | |
| //
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"crypto/tls"
 | |
| 	"errors"
 | |
| 	"net"
 | |
| 	"sort"
 | |
| 	"sync"
 | |
| 	"syscall"
 | |
| 	"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"
 | |
| 	utls "gitlab.com/yawning/utls.git"
 | |
| )
 | |
| 
 | |
| // ClientIDs to map configurable inputs to uTLS fingerprints
 | |
| // We use a non-zero index to map to each ClientID
 | |
| var ClientIDs = map[int]*utls.ClientHelloID{
 | |
| 	1: &utls.HelloGolang,
 | |
| 	2: &utls.HelloChrome_Auto,
 | |
| 	3: &utls.HelloFirefox_Auto,
 | |
| 	4: &utls.HelloIOS_Auto,
 | |
| }
 | |
| 
 | |
| // TLSTrace performs tracing using control and target SNI
 | |
| func (m *Measurer) TLSTrace(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger,
 | |
| 	address string, targetSNI string, trace *CompleteTrace) {
 | |
| 	// perform an iterative trace with the control SNI
 | |
| 	trace.ControlTrace = m.startIterativeTrace(ctx, index, zeroTime, logger, address, m.config.snicontrol())
 | |
| 	// perform an iterative trace with the target SNI
 | |
| 	trace.TargetTrace = m.startIterativeTrace(ctx, index, zeroTime, logger, address, targetSNI)
 | |
| }
 | |
| 
 | |
| // startIterativeTrace creates a Trace and calls iterativeTrace
 | |
| func (m *Measurer) startIterativeTrace(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger,
 | |
| 	address string, sni string) (tr *IterativeTrace) {
 | |
| 	tr = &IterativeTrace{
 | |
| 		SNI:        sni,
 | |
| 		Iterations: []*Iteration{},
 | |
| 	}
 | |
| 	maxTTL := m.config.maxttl()
 | |
| 	m.traceWithIncreasingTTLs(ctx, index, zeroTime, logger, address, sni, maxTTL, tr)
 | |
| 	tr.Iterations = alignIterations(tr.Iterations)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // traceWithIncreasingTTLs performs iterative tracing with increasing TTL values
 | |
| func (m *Measurer) traceWithIncreasingTTLs(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger,
 | |
| 	address string, sni string, maxTTL int64, trace *IterativeTrace) {
 | |
| 	ticker := time.NewTicker(m.config.delay())
 | |
| 	wg := new(sync.WaitGroup)
 | |
| 	for i := int64(1); i <= maxTTL; i++ {
 | |
| 		wg.Add(1)
 | |
| 		go m.handshakeWithTTL(ctx, index, zeroTime, logger, address, sni, int(i), trace, wg)
 | |
| 		<-ticker.C
 | |
| 	}
 | |
| 	wg.Wait()
 | |
| }
 | |
| 
 | |
| // handshakeWithTTL performs the TLS Handshake using the passed ttl value
 | |
| func (m *Measurer) handshakeWithTTL(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger,
 | |
| 	address string, sni string, ttl int, tr *IterativeTrace, wg *sync.WaitGroup) {
 | |
| 	defer wg.Done()
 | |
| 	trace := measurexlite.NewTrace(index, zeroTime)
 | |
| 	// 1. Connect to the target IP
 | |
| 	// TODO(DecFox, bassosimone): Do we need a trace for this TCP connect?
 | |
| 	d := NewDialerTTLWrapper()
 | |
| 	ol := measurexlite.NewOperationLogger(logger, "Handshake Trace #%d TTL %d %s %s", index, ttl, address, sni)
 | |
| 	conn, err := d.DialContext(ctx, "tcp", address)
 | |
| 	if err != nil {
 | |
| 		iteration := newIterationFromHandshake(ttl, err, nil, nil)
 | |
| 		tr.addIterations(iteration)
 | |
| 		ol.Stop(err)
 | |
| 		return
 | |
| 	}
 | |
| 	defer conn.Close()
 | |
| 	// 2. Set the TTL to the passed value
 | |
| 	err = setConnTTL(conn, ttl)
 | |
| 	if err != nil {
 | |
| 		iteration := newIterationFromHandshake(ttl, err, nil, nil)
 | |
| 		tr.addIterations(iteration)
 | |
| 		ol.Stop(err)
 | |
| 		return
 | |
| 	}
 | |
| 	// 3. Perform the handshake and extract the SO_ERROR value (if any)
 | |
| 	// Note: we switch to a uTLS Handshaker if the configured ClientID is non-zero
 | |
| 	thx := trace.NewTLSHandshakerStdlib(logger)
 | |
| 	clientId := m.config.clientid()
 | |
| 	if clientId > 0 {
 | |
| 		thx = trace.NewTLSHandshakerUTLS(logger, ClientIDs[clientId])
 | |
| 	}
 | |
| 	_, _, err = thx.Handshake(ctx, conn, genTLSConfig(sni))
 | |
| 	ol.Stop(err)
 | |
| 	soErr := extractSoError(conn)
 | |
| 	// 4. reset the TTL value to ensure that conn closes successfully
 | |
| 	// Note: Do not check for errors here
 | |
| 	_ = setConnTTL(conn, 64)
 | |
| 	iteration := newIterationFromHandshake(ttl, nil, soErr, trace.FirstTLSHandshakeOrNil())
 | |
| 	tr.addIterations(iteration)
 | |
| }
 | |
| 
 | |
| // extractSoError fetches the SO_ERROR value and returns a non-nil error if
 | |
| // it qualifies as a valid ICMP soft error
 | |
| // Note: The passed conn must be of type dialerTTLWrapperConn
 | |
| func extractSoError(conn net.Conn) error {
 | |
| 	soErrno, err := getSoErr(conn)
 | |
| 	if err != nil || errors.Is(soErrno, syscall.Errno(0)) {
 | |
| 		return nil
 | |
| 	}
 | |
| 	soErr := netxlite.MaybeNewErrWrapper(netxlite.ClassifyGenericError, netxlite.TLSHandshakeOperation, soErrno)
 | |
| 	return soErr
 | |
| }
 | |
| 
 | |
| // genTLSConfig generates tls.Config from a given SNI
 | |
| func genTLSConfig(sni string) *tls.Config {
 | |
| 	return &tls.Config{
 | |
| 		RootCAs:            netxlite.NewDefaultCertPool(),
 | |
| 		ServerName:         sni,
 | |
| 		NextProtos:         []string{"h2", "http/1.1"},
 | |
| 		InsecureSkipVerify: true,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // alignIterEvents sorts the iterEvents according to increasing TTL
 | |
| // and stops when we receive a nil or connection_reset
 | |
| func alignIterations(in []*Iteration) (out []*Iteration) {
 | |
| 	out = []*Iteration{}
 | |
| 	sort.Slice(in, func(i int, j int) bool {
 | |
| 		return in[i].TTL < in[j].TTL
 | |
| 	})
 | |
| 	for _, iter := range in {
 | |
| 		out = append(out, iter)
 | |
| 		if iter.Handshake.Failure == nil || *iter.Handshake.Failure == netxlite.FailureConnectionReset {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return out
 | |
| }
 |