Skip to content

Commit

Permalink
Add support for randomizing handshake order
Browse files Browse the repository at this point in the history
  • Loading branch information
hellais committed Nov 11, 2024
1 parent 186df09 commit 2ef3d7b
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 69 deletions.
47 changes: 47 additions & 0 deletions internal/experiment/echcheck/handshake.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,52 @@ import (

const echExtensionType uint16 = 0xfe0d

func connectAndHandshake(
ctx context.Context,
startTime time.Time,
address string, sni string, outerSni string,
logger model.Logger) (chan model.ArchivalTLSOrQUICHandshakeResult, error) {

channel := make(chan model.ArchivalTLSOrQUICHandshakeResult)

ol := logx.NewOperationLogger(logger, "echcheck: TCPConnect %s", address)
var dialer net.Dialer
conn, err := dialer.DialContext(ctx, "tcp", address)
ol.Stop(err)
if err != nil {
return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err)
}

go func() {
var res *model.ArchivalTLSOrQUICHandshakeResult
if outerSni == "" {
res = handshake(
ctx,
conn,
startTime,
address,
sni,
logger,
)
} else {
res = handshakeWithEch(
ctx,
conn,
startTime,
address,
outerSni,
logger,
)
// We need to set this explicitly because otherwise it will get
// overridden with the outerSni in the case of ECH
res.ServerName = sni
}
channel <- *res
}()

return channel, nil
}

func handshake(ctx context.Context, conn net.Conn, zeroTime time.Time,
address string, sni string, logger model.Logger) *model.ArchivalTLSOrQUICHandshakeResult {
return handshakeWithExtension(ctx, conn, zeroTime, address, sni, []utls.TLSExtension{}, logger)
Expand All @@ -36,6 +82,7 @@ func handshakeWithEch(ctx context.Context, conn net.Conn, zeroTime time.Time,

hs := handshakeWithExtension(ctx, conn, zeroTime, address, sni, []utls.TLSExtension{&utlsEchExtension}, logger)
hs.ECHConfig = "GREASE"
hs.OuterServerName = sni
return hs
}

Expand Down
108 changes: 39 additions & 69 deletions internal/experiment/echcheck/measure.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ package echcheck
import (
"context"
"errors"
"math/rand"
"net"
"net/url"
"time"

"github.com/ooni/probe-cli/v3/internal/logx"
"github.com/ooni/probe-cli/v3/internal/measurexlite"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/netxlite"
"github.com/ooni/probe-cli/v3/internal/runtimex"
)

Expand Down Expand Up @@ -76,80 +75,51 @@ func (m *Measurer) Run(
runtimex.Assert(len(addrs) > 0, "expected at least one entry in addrs")
address := net.JoinHostPort(addrs[0], "443")

// 2. Set up TCP connections
ol = logx.NewOperationLogger(args.Session.Logger(), "echcheck: TCPConnect#1 %s", address)
var dialer net.Dialer
conn, err := dialer.DialContext(ctx, "tcp", address)
ol.Stop(err)
if err != nil {
return netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err)
handshakes := []func() (chan model.ArchivalTLSOrQUICHandshakeResult, error){
// handshake with ECH disabled and SNI coming from the URL
func() (chan model.ArchivalTLSOrQUICHandshakeResult, error) {
return connectAndHandshake(ctx, args.Measurement.MeasurementStartTimeSaved,
address, parsed.Host, "", args.Session.Logger())
},
// handshake with ECH enabled and ClientHelloOuter SNI coming from the URL
func() (chan model.ArchivalTLSOrQUICHandshakeResult, error) {
return connectAndHandshake(ctx, args.Measurement.MeasurementStartTimeSaved,
address, parsed.Host, parsed.Host, args.Session.Logger())
},
// handshake with ECH enabled and hardcoded different ClientHelloOuter SNI
func() (chan model.ArchivalTLSOrQUICHandshakeResult, error) {
return connectAndHandshake(ctx, args.Measurement.MeasurementStartTimeSaved,
address, parsed.Host, "cloudflare.com", args.Session.Logger())
},
}

ol = logx.NewOperationLogger(args.Session.Logger(), "echcheck: TCPConnect#2 %s", address)
conn2, err := dialer.DialContext(ctx, "tcp", address)
ol.Stop(err)
if err != nil {
return netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err)
// We shuffle the order in which the operations are done to avoid residual
// censorship issues.
rand.Shuffle(len(handshakes), func(i, j int) {
handshakes[i], handshakes[j] = handshakes[j], handshakes[i]
})

var channels [3](chan model.ArchivalTLSOrQUICHandshakeResult)
var results [3](model.ArchivalTLSOrQUICHandshakeResult)

// Fire the handshakes in parallel
// TODO: currently if one of the connects fails we fail the whole result
// set. This is probably OK given that we only ever use the same address,
// but this may be something we want to change in the future.
for idx, hs := range handshakes {
channels[idx], err = hs()
if err != nil {
return err
}
}

ol = logx.NewOperationLogger(args.Session.Logger(), "echcheck: TCPConnect#3 %s", address)
conn3, err := dialer.DialContext(ctx, "tcp", address)
ol.Stop(err)
if err != nil {
return netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err)
// Wait on each channel for the results to come in
for idx, ch := range channels {
results[idx] = <-ch
}

// 3. Conduct and measure control and target TLS handshakes in parallel
noEchChannel := make(chan model.ArchivalTLSOrQUICHandshakeResult)
echWithMatchingOuterSniChannel := make(chan model.ArchivalTLSOrQUICHandshakeResult)
echWithExampleOuterSniChannel := make(chan model.ArchivalTLSOrQUICHandshakeResult)
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()

go func() {
noEchChannel <- *handshake(
ctx,
conn,
args.Measurement.MeasurementStartTimeSaved,
address,
parsed.Host,
args.Session.Logger(),
)
}()

go func() {
echWithMatchingOuterSniChannel <- *handshakeWithEch(
ctx,
conn2,
args.Measurement.MeasurementStartTimeSaved,
address,
parsed.Host,
args.Session.Logger(),
)
}()

exampleSni := "cloudflare.com"
go func() {
echWithExampleOuterSniChannel <- *handshakeWithEch(
ctx,
conn3,
args.Measurement.MeasurementStartTimeSaved,
address,
exampleSni,
args.Session.Logger(),
)
}()

noEch := <-noEchChannel
echWithMatchingOuterSni := <-echWithMatchingOuterSniChannel
echWithMatchingOuterSni.ServerName = parsed.Host
echWithMatchingOuterSni.OuterServerName = parsed.Host
echWithExampleOuterSni := <-echWithExampleOuterSniChannel
echWithExampleOuterSni.ServerName = parsed.Host
echWithExampleOuterSni.OuterServerName = exampleSni

args.Measurement.TestKeys = TestKeys{TLSHandshakes: []*model.ArchivalTLSOrQUICHandshakeResult{
&noEch, &echWithMatchingOuterSni, &echWithExampleOuterSni,
&results[0], &results[1], &results[2],
}}

return nil
Expand Down

0 comments on commit 2ef3d7b

Please sign in to comment.