Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to using cloudflare-ech.com as the target for the ech test #1658

Merged
merged 4 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion 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 @@ -34,7 +80,10 @@ func handshakeWithEch(ctx context.Context, conn net.Conn, zeroTime time.Time,
utlsEchExtension.Id = echExtensionType
utlsEchExtension.Data = payload

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

func handshakeMaybePrintWithECH(doprint bool) string {
Expand Down
96 changes: 46 additions & 50 deletions internal/experiment/echcheck/measure.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,20 @@ 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"
)

const (
testName = "echcheck"
testVersion = "0.1.2"
defaultURL = "https://crypto.cloudflare.com/cdn-cgi/trace"
testVersion = "0.2.0"
defaultURL = "https://cloudflare-ech.com/cdn-cgi/trace"
)

var (
Expand All @@ -30,8 +29,7 @@ var (

// TestKeys contains echcheck test keys.
type TestKeys struct {
Control model.ArchivalTLSOrQUICHandshakeResult `json:"control"`
Target model.ArchivalTLSOrQUICHandshakeResult `json:"target"`
TLSHandshakes []*model.ArchivalTLSOrQUICHandshakeResult `json:"tls_handshakes"`
}

// Measurer performs the measurement.
Expand Down Expand Up @@ -77,54 +75,52 @@ 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
}
}

// 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
controlChannel := make(chan model.ArchivalTLSOrQUICHandshakeResult)
targetChannel := make(chan model.ArchivalTLSOrQUICHandshakeResult)
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()

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

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

control := <-controlChannel
target := <-targetChannel

args.Measurement.TestKeys = TestKeys{Control: control, Target: target}
args.Measurement.TestKeys = TestKeys{TLSHandshakes: []*model.ArchivalTLSOrQUICHandshakeResult{
&results[0], &results[1], &results[2],
}}

return nil
}
Expand Down
13 changes: 8 additions & 5 deletions internal/experiment/echcheck/measure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,13 @@ func TestMeasurementSuccessRealWorld(t *testing.T) {

// check results
tk := msrmnt.TestKeys.(TestKeys)
if tk.Control.Failure != nil {
t.Fatal("unexpected control failure:", *tk.Control.Failure)
}
if tk.Target.Failure != nil {
t.Fatal("unexpected target failure:", *tk.Target.Failure)
for _, hs := range tk.TLSHandshakes {
if hs.Failure != nil {
if hs.ECHConfig == "GREASE" {
t.Fatal("unexpected exp failure:", hs.Failure)
} else {
t.Fatal("unexpected ctrl failure:", hs.Failure)
}
}
}
}
2 changes: 2 additions & 0 deletions internal/model/archival.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ type ArchivalTLSOrQUICHandshakeResult struct {
NoTLSVerify bool `json:"no_tls_verify"`
PeerCertificates []ArchivalBinaryData `json:"peer_certificates"`
ServerName string `json:"server_name"`
OuterServerName string `json:"outer_server_name,omitempty"`
ECHConfig string `json:"echconfig,omitempty"`
T0 float64 `json:"t0,omitempty"`
T float64 `json:"t"`
Tags []string `json:"tags"`
Expand Down
Loading