Skip to content

Commit

Permalink
feat: add support for quic
Browse files Browse the repository at this point in the history
  • Loading branch information
bassosimone committed Oct 19, 2023
1 parent c349c82 commit c5f3744
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 3 deletions.
20 changes: 20 additions & 0 deletions internal/cmd/pdsl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,22 @@ func httpsExperiment(ctx context.Context, rt pdsl.Runtime, domain pdsl.DomainNam
return pdsl.Merge(pdsl.Fork(2, pdsl.TLSHandshake(ctx, rt, tlsConfig), conns)...)
}

func http3Experiment(ctx context.Context, rt pdsl.Runtime, domain pdsl.DomainName,
ipAddrs ...pdsl.Result[pdsl.IPAddr]) <-chan pdsl.Result[pdsl.QUICConn] {
// create a suitable TLS configuration
tlsConfig := &tls.Config{
NextProtos: []string{"h3"},
ServerName: string(domain),
RootCAs: nil, // use netxlite default CA
}

// convert the IP addresses to endpoints
endpoints := pdsl.MakeEndpointsForPort("443")(pdsl.Stream(ipAddrs...))

// create QUIC connections also using a goroutine pool
return pdsl.Merge(pdsl.Fork(2, pdsl.QUICHandshake(ctx, rt, tlsConfig), endpoints)...)
}

func mainExperiment(ctx context.Context, rt pdsl.Runtime, domain pdsl.DomainName) {
// run the DNS experiment until completion
ipAddrs := dnsExperiment(ctx, rt, domain)
Expand All @@ -60,10 +76,14 @@ func mainExperiment(ctx context.Context, rt pdsl.Runtime, domain pdsl.DomainName
// start the HTTP experiment
tcpConns := httpExperiment(ctx, rt, domain, ipAddrs...)

// start the HTTP/3 experiment
quicConns := http3Experiment(ctx, rt, domain, ipAddrs...)

// wait for both experiments to terminate
_ = pdsl.Collect(
pdsl.Discard[pdsl.TLSConn]()(tlsConns),
pdsl.Discard[pdsl.TCPConn]()(tcpConns),
pdsl.Discard[pdsl.QUICConn]()(quicConns),
)
}

Expand Down
71 changes: 71 additions & 0 deletions internal/pdsl/quichandshake.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package pdsl

import (
"context"
"crypto/tls"
"time"

"github.com/ooni/probe-cli/v3/internal/logx"
"github.com/ooni/probe-cli/v3/internal/netxlite"
"github.com/quic-go/quic-go"
)

// QUICConn is the [model.QUICConn] produced by [QUICHandshake].
type QUICConn struct {
Trace Trace
quic.EarlyConnection
}

// TCPConnect returns a [Filter] that attempts to create [QUICConn] from [TCPConn].
func QUICHandshake(ctx context.Context, rt Runtime, tlsConfig *tls.Config, tags ...string) Filter[Endpoint, QUICConn] {
return startFilterService(func(endpoint Endpoint) (QUICConn, error) {
sni := tlsConfig.ServerName
alpn := tlsConfig.NextProtos

// start operation logger
traceID := rt.NewTraceID()
ol := logx.NewOperationLogger(
rt.Logger(),
"[#%d] QUICHandshake %s SNI=%s ALPN=%s",
traceID,
endpoint,
sni,
alpn,
)

// create trace for collecting OONI observations
trace := rt.NewTrace(traceID, rt.ZeroTime(), tags...)

// enforce a timeout
const timeout = 10 * time.Second
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

// QUIC handshake
udpListener := netxlite.NewUDPListener()
thx := trace.NewQUICDialerWithoutResolver(udpListener, rt.Logger())
quicConn, err := thx.DialContext(ctx, string(endpoint), tlsConfig, &quic.Config{})

// stop the operation logger
ol.Stop(err)

// handle failure
if err != nil {
return QUICConn{}, err
}

// make sure the Runtime eventually closes the connection
rt.RegisterCloser(&quicCloserConn{quicConn})

// handle success
return QUICConn{trace, quicConn}, nil
})
}

type quicCloserConn struct {
quic.EarlyConnection
}

func (c *quicCloserConn) Close() error {
return c.CloseWithError(0, "")
}
5 changes: 2 additions & 3 deletions internal/pdsl/tlshandshake.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,11 @@ func TLSHandshake(ctx context.Context, rt Runtime, tlsConfig *tls.Config) Filter
endpoint := tcpConn.RemoteAddr().String()
trace := tcpConn.Trace

// start the operation logger
traceID := rt.NewTraceID()
// start operation logger
ol := logx.NewOperationLogger(
rt.Logger(),
"[#%d] TLSHandshake %s SNI=%s ALPN=%s",
traceID,
0, // TODO
endpoint,
sni,
alpn,
Expand Down
9 changes: 9 additions & 0 deletions internal/pdsl/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ type Trace interface {
// interface, but they're not used by this function.
NewDialerWithoutResolver(dl model.DebugLogger, wrappers ...model.DialerWrapper) model.Dialer

// NewQUICDialerWithoutResolver is equivalent to
// netxlite.NewQUICDialerWithoutResolver except that it returns a
// model.QUICDialer that uses this trace.
//
// Caveat: the dialer wrappers are there to implement the
// model.MeasuringNetwork interface, but they're not used by this function.
NewQUICDialerWithoutResolver(listener model.UDPListener,
dl model.DebugLogger, wrappers ...model.QUICDialerWrapper) model.QUICDialer

// NewStdlibResolver returns a resolver that saves observations into this trace.
NewStdlibResolver(logger model.DebugLogger) model.Resolver

Expand Down

0 comments on commit c5f3744

Please sign in to comment.