From ceca00a66412722b3aa631ea11c4a1327344431a Mon Sep 17 00:00:00 2001 From: decfox Date: Thu, 30 Jul 2026 13:53:25 +0530 Subject: [PATCH 1/2] chore: upgrade quic-go to v0.56.0 --- go.mod | 8 +- go.sum | 13 +- internal/legacy/measurex/http.go | 3 +- internal/legacy/measurex/measurer.go | 2 +- internal/legacy/measurex/quic.go | 2 +- internal/legacy/tracex/quic.go | 4 +- internal/legacy/tracex/quic_test.go | 6 +- internal/measurexlite/quic.go | 8 +- internal/measurexlite/quic_test.go | 6 +- internal/mocks/quic.go | 100 +++--------- internal/mocks/quic_test.go | 163 ++----------------- internal/mocks/trace.go | 4 +- internal/mocks/trace_test.go | 2 +- internal/model/netx.go | 33 +++- internal/netxlite/http3.go | 22 ++- internal/netxlite/http3_test.go | 2 +- internal/netxlite/quic.go | 63 ++++--- internal/netxlite/quic_test.go | 44 ++--- internal/netxlite/trace.go | 2 +- internal/tutorial/netxlite/chapter04/main.go | 3 +- internal/tutorial/netxlite/chapter08/main.go | 3 +- internal/x/dslvm/quic.go | 2 +- internal/x/dslx/http_test.go | 2 +- internal/x/dslx/quic.go | 5 +- internal/x/dslx/quic_test.go | 8 +- internal/x/dslx/runtimeminimal_test.go | 2 +- 26 files changed, 193 insertions(+), 319 deletions(-) diff --git a/go.mod b/go.mod index f01dc215e2..bb6b865278 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/pion/stun v0.6.1 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.24.1 - github.com/quic-go/quic-go v0.43.1 + github.com/quic-go/quic-go v0.56.0 github.com/rogpeppe/go-internal v1.15.0 github.com/rubenv/sql-migrate v1.8.1 github.com/schollz/progressbar/v3 v3.19.1 @@ -105,7 +105,6 @@ require ( github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/mock v1.7.0-rc.1 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -126,7 +125,7 @@ require ( github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mroth/weightedrand v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/onsi/ginkgo/v2 v2.12.0 // indirect + github.com/onsi/gomega v1.27.10 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pion/datachannel v1.6.0 // indirect @@ -155,7 +154,7 @@ require ( github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.70.1 // indirect github.com/prometheus/procfs v0.21.1 // indirect - github.com/quic-go/qpack v0.4.0 // indirect + github.com/quic-go/qpack v0.5.1 // indirect github.com/realclientip/realclientip-go v1.0.0 // indirect github.com/refraction-networking/conjure v0.7.11-0.20240130155008-c8df96195ab2 // indirect github.com/refraction-networking/ed25519 v0.1.2 // indirect @@ -187,7 +186,6 @@ require ( gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect gitlab.com/yawning/edwards25519-extra v0.0.0-20231005122941-2149dcafc266 // indirect gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/ptutil v0.0.0-20240710081135-6c4d8ed41027 // indirect - go.uber.org/mock v0.4.0 // indirect golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect golang.org/x/mod v0.37.0 // indirect golang.org/x/sync v0.22.0 // indirect diff --git a/go.sum b/go.sum index 0642df59ce..0f8518fff1 100644 --- a/go.sum +++ b/go.sum @@ -326,6 +326,7 @@ github.com/mroth/weightedrand v1.0.0/go.mod h1:3p2SIcC8al1YMzGhAIoXD+r9olo/g/cdJ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -424,10 +425,10 @@ github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc= github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= -github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= -github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/quic-go v0.43.1 h1:fLiMNfQVe9q2JvSsiXo4fXOEguXHGGl9+6gLp4RPeZQ= -github.com/quic-go/quic-go v0.43.1/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.56.0 h1:q/TW+OLismmXAehgFLczhCDTYB3bFmua4D9lsNBWxvY= +github.com/quic-go/quic-go v0.56.0/go.mod h1:9gx5KsFQtw2oZ6GZTyh+7YEvOxWCL9WZAepnHxgAo6c= github.com/realclientip/realclientip-go v1.0.0 h1:+yPxeC0mEaJzq1BfCt2h4BxlyrvIIBzR6suDc3BEF1U= github.com/realclientip/realclientip-go v1.0.0/go.mod h1:CXnUdVwFRcXFJIRb/dTYqbT7ud48+Pi2pFm80bxDmcI= github.com/refraction-networking/conjure v0.7.11-0.20240130155008-c8df96195ab2 h1:m2ZH6WV69otVmBpWbk8et3MypHFsjcYXTNrknQKS/PY= @@ -573,8 +574,8 @@ gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2 v2.1 gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2 v2.10.1/go.mod h1:DI4jAA1yfL9jzwDsSuW6D5ePrDCzEArQ58vdhKWHxDA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= +go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= diff --git a/internal/legacy/measurex/http.go b/internal/legacy/measurex/http.go index 53c9949665..a4c051c0a2 100644 --- a/internal/legacy/measurex/http.go +++ b/internal/legacy/measurex/http.go @@ -28,7 +28,6 @@ import ( "github.com/ooni/probe-cli/v3/internal/model" "github.com/ooni/probe-cli/v3/internal/netxlite" "github.com/ooni/probe-cli/v3/internal/runtimex" - "github.com/quic-go/quic-go" "golang.org/x/net/publicsuffix" ) @@ -95,7 +94,7 @@ func (mx *Measurer) NewHTTPTransportWithTLSConn( // NewHTTPTransportWithQUICConn creates and wraps an HTTPTransport that // does not dial and only uses the given QUIC connection. func (mx *Measurer) NewHTTPTransportWithQUICConn( - logger model.Logger, db WritableDB, qconn quic.EarlyConnection) *HTTPTransportDB { + logger model.Logger, db WritableDB, qconn model.QUICConn) *HTTPTransportDB { return mx.WrapHTTPTransport(db, netxlite.NewHTTP3Transport( logger, netxlite.NewSingleUseQUICDialer(qconn), &tls.Config{}, // #nosec G402 - we need to use a large TLS versions range for measuring diff --git a/internal/legacy/measurex/measurer.go b/internal/legacy/measurex/measurer.go index 7ef50a6a81..f4d71397ae 100644 --- a/internal/legacy/measurex/measurer.go +++ b/internal/legacy/measurex/measurer.go @@ -417,7 +417,7 @@ func (mx *Measurer) quicHandshakeTimeout() time.Duration { // db to store events rather than creating a temporary one and // use it to generate a new Measurement. func (mx *Measurer) QUICHandshakeWithDB(ctx context.Context, db WritableDB, - address string, config *tls.Config) (quic.EarlyConnection, error) { + address string, config *tls.Config) (model.QUICConn, error) { timeout := mx.quicHandshakeTimeout() ol := NewOperationLogger(mx.Logger, "QUICHandshake %s with sni=%s", address, config.ServerName) diff --git a/internal/legacy/measurex/quic.go b/internal/legacy/measurex/quic.go index 6dc5384ee3..26dbb21d17 100644 --- a/internal/legacy/measurex/quic.go +++ b/internal/legacy/measurex/quic.go @@ -106,7 +106,7 @@ type quicDialerDB struct { } func (qh *quicDialerDB) DialContext(ctx context.Context, address string, - tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) { + tlsConfig *tls.Config, quicConfig *quic.Config) (model.QUICConn, error) { started := time.Since(qh.begin).Seconds() var state tls.ConnectionState netx := &netxlite.Netx{} diff --git a/internal/legacy/tracex/quic.go b/internal/legacy/tracex/quic.go index 0d492685e7..5ca779dc21 100644 --- a/internal/legacy/tracex/quic.go +++ b/internal/legacy/tracex/quic.go @@ -41,7 +41,7 @@ func (s *Saver) WrapQUICDialer(qd model.QUICDialer) model.QUICDialer { // DialContext implements QUICDialer.DialContext func (h *QUICDialerSaver) DialContext(ctx context.Context, - host string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { + host string, tlsCfg *tls.Config, cfg *quic.Config) (model.QUICConn, error) { start := time.Now() // TODO(bassosimone): in the future we probably want to also save // information about what versions we're willing to accept. @@ -92,7 +92,7 @@ func (h *QUICDialerSaver) CloseIdleConnections() { } // quicConnectionState returns the ConnectionState of a QUIC Session. -func quicConnectionState(sess quic.EarlyConnection) tls.ConnectionState { +func quicConnectionState(sess model.QUICConn) tls.ConnectionState { return sess.ConnectionState().TLS } diff --git a/internal/legacy/tracex/quic_test.go b/internal/legacy/tracex/quic_test.go index 7530b04ef7..6f6c24b470 100644 --- a/internal/legacy/tracex/quic_test.go +++ b/internal/legacy/tracex/quic_test.go @@ -78,7 +78,7 @@ func TestQUICDialerSaver(t *testing.T) { t.Run("on success", func(t *testing.T) { saver := &Saver{} - returnedConn := &mocks.QUICEarlyConnection{ + returnedConn := &mocks.QUICConn{ MockConnectionState: func() quic.ConnectionState { cs := quic.ConnectionState{} cs.TLS.CipherSuite = tls.TLS_RSA_WITH_RC4_128_SHA @@ -92,7 +92,7 @@ func TestQUICDialerSaver(t *testing.T) { } dialer := saver.WrapQUICDialer(&mocks.QUICDialer{ MockDialContext: func(ctx context.Context, address string, - tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) { + tlsConfig *tls.Config, quicConfig *quic.Config) (model.QUICConn, error) { return returnedConn, nil }, }) @@ -132,7 +132,7 @@ func TestQUICDialerSaver(t *testing.T) { saver := &Saver{} dialer := saver.WrapQUICDialer(&mocks.QUICDialer{ MockDialContext: func(ctx context.Context, address string, - tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) { + tlsConfig *tls.Config, quicConfig *quic.Config) (model.QUICConn, error) { return nil, expected }, }) diff --git a/internal/measurexlite/quic.go b/internal/measurexlite/quic.go index 03de1b5bd8..2a053155a4 100644 --- a/internal/measurexlite/quic.go +++ b/internal/measurexlite/quic.go @@ -38,7 +38,7 @@ var _ model.QUICDialer = &quicDialerTrace{} // DialContext implements model.QUICDialer.DialContext. func (qdx *quicDialerTrace) DialContext(ctx context.Context, address string, tlsConfig *tls.Config, quicConfig *quic.Config) ( - quic.EarlyConnection, error) { + model.QUICConn, error) { // TODO(https://github.com/ooni/probe/issues/2665) return qdx.qd.DialContext(netxlite.ContextWithTrace(ctx, qdx.tx), address, tlsConfig, quicConfig) } @@ -59,7 +59,7 @@ func (tx *Trace) OnQUICHandshakeStart(now time.Time, remoteAddr string, config * } // OnQUICHandshakeDone implements model.Trace.OnQUICHandshakeDone -func (tx *Trace) OnQUICHandshakeDone(started time.Time, remoteAddr string, qconn quic.EarlyConnection, +func (tx *Trace) OnQUICHandshakeDone(started time.Time, remoteAddr string, qconn model.QUICConn, config *tls.Config, err error, finished time.Time) { t := finished.Sub(tx.ZeroTime()) @@ -112,8 +112,8 @@ func (tx *Trace) FirstQUICHandshakeOrNil() *model.ArchivalTLSOrQUICHandshakeResu return ev[0] } -// MaybeCloseQUICConn is a convenience function for closing a [quic.EarlyConnection] when it is not nil. -func MaybeCloseQUICConn(conn quic.EarlyConnection) (err error) { +// MaybeCloseQUICConn is a convenience function for closing a [model.QUICConn] when it is not nil. +func MaybeCloseQUICConn(conn model.QUICConn) (err error) { if conn != nil { err = conn.CloseWithError(0, "") } diff --git a/internal/measurexlite/quic_test.go b/internal/measurexlite/quic_test.go index eede303e63..959e32411f 100644 --- a/internal/measurexlite/quic_test.go +++ b/internal/measurexlite/quic_test.go @@ -46,7 +46,7 @@ func TestNewQUICDialerWithoutResolver(t *testing.T) { var hasCorrectTrace bool underlying := &mocks.QUICDialer{ MockDialContext: func(ctx context.Context, address string, tlsConfig *tls.Config, - quicConfig *quic.Config) (quic.EarlyConnection, error) { + quicConfig *quic.Config) (model.QUICConn, error) { gotTrace := netxlite.ContextTraceOrDefault(ctx) hasCorrectTrace = (gotTrace == trace) return nil, expectedErr @@ -370,7 +370,7 @@ func TestFirstQUICHandshake(t *testing.T) { func TestMaybeCloseQUICConn(t *testing.T) { type closeQuicTest struct { name string - input quic.EarlyConnection + input model.QUICConn called bool } var called bool @@ -383,7 +383,7 @@ func TestMaybeCloseQUICConn(t *testing.T) { }, { name: "with nonnil conn", - input: &mocks.QUICEarlyConnection{ + input: &mocks.QUICConn{ MockCloseWithError: func(code quic.ApplicationErrorCode, reason string) error { called = true return nil diff --git a/internal/mocks/quic.go b/internal/mocks/quic.go index f6c0c7c392..b6fa4c5bfe 100644 --- a/internal/mocks/quic.go +++ b/internal/mocks/quic.go @@ -15,7 +15,7 @@ import ( type QUICDialer struct { // MockDialContext allows mocking DialContext. MockDialContext func(ctx context.Context, address string, - tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) + tlsConfig *tls.Config, quicConfig *quic.Config) (model.QUICConn, error) // MockCloseIdleConnections allows mocking CloseIdleConnections. MockCloseIdleConnections func() @@ -25,7 +25,7 @@ var _ model.QUICDialer = &QUICDialer{} // DialContext calls MockDialContext. func (qcd *QUICDialer) DialContext(ctx context.Context, address string, - tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) { + tlsConfig *tls.Config, quicConfig *quic.Config) (model.QUICConn, error) { return qcd.MockDialContext(ctx, address, tlsConfig, quicConfig) } @@ -34,101 +34,47 @@ func (qcd *QUICDialer) CloseIdleConnections() { qcd.MockCloseIdleConnections() } -// QUICEarlyConnection is a mockable quic.EarlyConnection. -type QUICEarlyConnection struct { - MockAcceptStream func(context.Context) (quic.Stream, error) - MockAcceptUniStream func(context.Context) (quic.ReceiveStream, error) - MockOpenStream func() (quic.Stream, error) - MockOpenStreamSync func(ctx context.Context) (quic.Stream, error) - MockOpenUniStream func() (quic.SendStream, error) - MockOpenUniStreamSync func(ctx context.Context) (quic.SendStream, error) +// QUICConn is a mockable model.QUICConn. +type QUICConn struct { + MockCloseWithError func(code quic.ApplicationErrorCode, reason string) error + MockHandshakeComplete func() <-chan struct{} + MockConnectionState func() quic.ConnectionState MockLocalAddr func() net.Addr MockRemoteAddr func() net.Addr - MockCloseWithError func(code quic.ApplicationErrorCode, reason string) error MockContext func() context.Context - MockConnectionState func() quic.ConnectionState - MockHandshakeComplete func() <-chan struct{} - MockNextConnection func() quic.Connection - MockSendDatagram func(b []byte) error - MockReceiveDatagram func(ctx context.Context) ([]byte, error) -} - -var _ quic.EarlyConnection = &QUICEarlyConnection{} - -// AcceptStream calls MockAcceptStream. -func (s *QUICEarlyConnection) AcceptStream(ctx context.Context) (quic.Stream, error) { - return s.MockAcceptStream(ctx) } -// AcceptUniStream calls MockAcceptUniStream. -func (s *QUICEarlyConnection) AcceptUniStream(ctx context.Context) (quic.ReceiveStream, error) { - return s.MockAcceptUniStream(ctx) -} - -// OpenStream calls MockOpenStream. -func (s *QUICEarlyConnection) OpenStream() (quic.Stream, error) { - return s.MockOpenStream() -} - -// OpenStreamSync calls MockOpenStreamSync. -func (s *QUICEarlyConnection) OpenStreamSync(ctx context.Context) (quic.Stream, error) { - return s.MockOpenStreamSync(ctx) -} - -// OpenUniStream calls MockOpenUniStream. -func (s *QUICEarlyConnection) OpenUniStream() (quic.SendStream, error) { - return s.MockOpenUniStream() -} - -// OpenUniStreamSync calls MockOpenUniStreamSync. -func (s *QUICEarlyConnection) OpenUniStreamSync(ctx context.Context) (quic.SendStream, error) { - return s.MockOpenUniStreamSync(ctx) -} - -// LocalAddr class MockLocalAddr. -func (c *QUICEarlyConnection) LocalAddr() net.Addr { - return c.MockLocalAddr() -} - -// RemoteAddr calls MockRemoteAddr. -func (c *QUICEarlyConnection) RemoteAddr() net.Addr { - return c.MockRemoteAddr() -} +var _ model.QUICConn = &QUICConn{} // CloseWithError calls MockCloseWithError. -func (c *QUICEarlyConnection) CloseWithError( +func (c *QUICConn) CloseWithError( code quic.ApplicationErrorCode, reason string) error { return c.MockCloseWithError(code, reason) } -// Context calls MockContext. -func (s *QUICEarlyConnection) Context() context.Context { - return s.MockContext() +// HandshakeComplete calls MockHandshakeComplete. +func (c *QUICConn) HandshakeComplete() <-chan struct{} { + return c.MockHandshakeComplete() } // ConnectionState calls MockConnectionState. -func (s *QUICEarlyConnection) ConnectionState() quic.ConnectionState { - return s.MockConnectionState() +func (c *QUICConn) ConnectionState() quic.ConnectionState { + return c.MockConnectionState() } -// HandshakeComplete calls MockHandshakeComplete. -func (s *QUICEarlyConnection) HandshakeComplete() <-chan struct{} { - return s.MockHandshakeComplete() -} - -// NextConnection calls MockNextConnection. -func (s *QUICEarlyConnection) NextConnection() quic.Connection { - return s.MockNextConnection() +// LocalAddr calls MockLocalAddr. +func (c *QUICConn) LocalAddr() net.Addr { + return c.MockLocalAddr() } -// SendDatagram calls MockSendDatagram. -func (s *QUICEarlyConnection) SendDatagram(b []byte) error { - return s.MockSendDatagram(b) +// RemoteAddr calls MockRemoteAddr. +func (c *QUICConn) RemoteAddr() net.Addr { + return c.MockRemoteAddr() } -// ReceiveDatagram calls MockReceiveDatagram. -func (s *QUICEarlyConnection) ReceiveDatagram(ctx context.Context) ([]byte, error) { - return s.MockReceiveDatagram(ctx) +// Context calls MockContext. +func (c *QUICConn) Context() context.Context { + return c.MockContext() } // UDPLikeConn is an UDP conn used by QUIC. diff --git a/internal/mocks/quic_test.go b/internal/mocks/quic_test.go index aafc0d4870..63befa7762 100644 --- a/internal/mocks/quic_test.go +++ b/internal/mocks/quic_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/ooni/probe-cli/v3/internal/model" "github.com/quic-go/quic-go" ) @@ -18,7 +19,7 @@ func TestQUICDialer(t *testing.T) { t.Run("DialContext", func(t *testing.T) { expected := errors.New("mocked error") qcd := &QUICDialer{ - MockDialContext: func(ctx context.Context, address string, tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) { + MockDialContext: func(ctx context.Context, address string, tlsConfig *tls.Config, quicConfig *quic.Config) (model.QUICConn, error) { return nil, expected }, } @@ -48,109 +49,9 @@ func TestQUICDialer(t *testing.T) { }) } -func TestQUICEarlyConnection(t *testing.T) { - t.Run("AcceptStream", func(t *testing.T) { - expected := errors.New("mocked error") - qconn := &QUICEarlyConnection{ - MockAcceptStream: func(ctx context.Context) (quic.Stream, error) { - return nil, expected - }, - } - ctx := context.Background() - stream, err := qconn.AcceptStream(ctx) - if !errors.Is(err, expected) { - t.Fatal("not the error we expected", err) - } - if stream != nil { - t.Fatal("expected nil stream here") - } - }) - - t.Run("AcceptUniStream", func(t *testing.T) { - expected := errors.New("mocked error") - qconn := &QUICEarlyConnection{ - MockAcceptUniStream: func(ctx context.Context) (quic.ReceiveStream, error) { - return nil, expected - }, - } - ctx := context.Background() - stream, err := qconn.AcceptUniStream(ctx) - if !errors.Is(err, expected) { - t.Fatal("not the error we expected", err) - } - if stream != nil { - t.Fatal("expected nil stream here") - } - }) - - t.Run("OpenStream", func(t *testing.T) { - expected := errors.New("mocked error") - qconn := &QUICEarlyConnection{ - MockOpenStream: func() (quic.Stream, error) { - return nil, expected - }, - } - stream, err := qconn.OpenStream() - if !errors.Is(err, expected) { - t.Fatal("not the error we expected", err) - } - if stream != nil { - t.Fatal("expected nil stream here") - } - }) - - t.Run("OpenStreamSync", func(t *testing.T) { - expected := errors.New("mocked error") - qconn := &QUICEarlyConnection{ - MockOpenStreamSync: func(ctx context.Context) (quic.Stream, error) { - return nil, expected - }, - } - ctx := context.Background() - stream, err := qconn.OpenStreamSync(ctx) - if !errors.Is(err, expected) { - t.Fatal("not the error we expected", err) - } - if stream != nil { - t.Fatal("expected nil stream here") - } - }) - - t.Run("OpenUniStream", func(t *testing.T) { - expected := errors.New("mocked error") - qconn := &QUICEarlyConnection{ - MockOpenUniStream: func() (quic.SendStream, error) { - return nil, expected - }, - } - stream, err := qconn.OpenUniStream() - if !errors.Is(err, expected) { - t.Fatal("not the error we expected", err) - } - if stream != nil { - t.Fatal("expected nil stream here") - } - }) - - t.Run("OpenUniStreamSync", func(t *testing.T) { - expected := errors.New("mocked error") - qconn := &QUICEarlyConnection{ - MockOpenUniStreamSync: func(ctx context.Context) (quic.SendStream, error) { - return nil, expected - }, - } - ctx := context.Background() - stream, err := qconn.OpenUniStreamSync(ctx) - if !errors.Is(err, expected) { - t.Fatal("not the error we expected", err) - } - if stream != nil { - t.Fatal("expected nil stream here") - } - }) - +func TestQUICConn(t *testing.T) { t.Run("LocalAddr", func(t *testing.T) { - qconn := &QUICEarlyConnection{ + qconn := &QUICConn{ MockLocalAddr: func() net.Addr { return &net.UDPAddr{} }, @@ -162,7 +63,7 @@ func TestQUICEarlyConnection(t *testing.T) { }) t.Run("RemoteAddr", func(t *testing.T) { - qconn := &QUICEarlyConnection{ + qconn := &QUICConn{ MockRemoteAddr: func() net.Addr { return &net.UDPAddr{} }, @@ -175,7 +76,7 @@ func TestQUICEarlyConnection(t *testing.T) { t.Run("CloseWithError", func(t *testing.T) { expected := errors.New("mocked error") - qconn := &QUICEarlyConnection{ + qconn := &QUICConn{ MockCloseWithError: func( code quic.ApplicationErrorCode, reason string) error { return expected @@ -189,7 +90,7 @@ func TestQUICEarlyConnection(t *testing.T) { t.Run("Context", func(t *testing.T) { ctx := context.Background() - qconn := &QUICEarlyConnection{ + qconn := &QUICConn{ MockContext: func() context.Context { return ctx }, @@ -201,8 +102,8 @@ func TestQUICEarlyConnection(t *testing.T) { }) t.Run("ConnectionState", func(t *testing.T) { - state := quic.ConnectionState{SupportsDatagrams: true} - qconn := &QUICEarlyConnection{ + state := quic.ConnectionState{Used0RTT: true} + qconn := &QUICConn{ MockConnectionState: func() quic.ConnectionState { return state }, @@ -215,7 +116,7 @@ func TestQUICEarlyConnection(t *testing.T) { t.Run("HandshakeComplete", func(t *testing.T) { ctx := context.Background() - qconn := &QUICEarlyConnection{ + qconn := &QUICConn{ MockHandshakeComplete: func() <-chan struct{} { return ctx.Done() }, @@ -225,50 +126,6 @@ func TestQUICEarlyConnection(t *testing.T) { t.Fatal("not the channel we expected") } }) - - t.Run("NextConnection", func(t *testing.T) { - next := &QUICEarlyConnection{} - qconn := &QUICEarlyConnection{ - MockNextConnection: func() quic.Connection { - return next - }, - } - out := qconn.NextConnection() - if !reflect.DeepEqual(next, out) { - t.Fatal("not the context we expected") - } - }) - - t.Run("SendDatagram", func(t *testing.T) { - expected := errors.New("mocked error") - qconn := &QUICEarlyConnection{ - MockSendDatagram: func(b []byte) error { - return expected - }, - } - b := make([]byte, 17) - err := qconn.SendDatagram(b) - if !errors.Is(err, expected) { - t.Fatal("not the error we expected", err) - } - }) - - t.Run("ReceiveDatagram", func(t *testing.T) { - expected := errors.New("mocked error") - ctx := context.Background() - qconn := &QUICEarlyConnection{ - MockReceiveDatagram: func(ctx context.Context) ([]byte, error) { - return nil, expected - }, - } - b, err := qconn.ReceiveDatagram(ctx) - if !errors.Is(err, expected) { - t.Fatal("not the error we expected", err) - } - if b != nil { - t.Fatal("expected nil buffer here") - } - }) } func TestQUICUDPLikeConn(t *testing.T) { diff --git a/internal/mocks/trace.go b/internal/mocks/trace.go index 051b3a1b77..0ead199e01 100644 --- a/internal/mocks/trace.go +++ b/internal/mocks/trace.go @@ -37,7 +37,7 @@ type Trace struct { MockOnQUICHandshakeStart func(now time.Time, remoteAddrs string, config *quic.Config) - MockOnQUICHandshakeDone func(started time.Time, remoteAddr string, qconn quic.EarlyConnection, + MockOnQUICHandshakeDone func(started time.Time, remoteAddr string, qconn model.QUICConn, config *tls.Config, err error, finished time.Time) } @@ -83,7 +83,7 @@ func (t *Trace) OnQUICHandshakeStart(now time.Time, remoteAddr string, config *q t.MockOnQUICHandshakeStart(now, remoteAddr, config) } -func (t *Trace) OnQUICHandshakeDone(started time.Time, remoteAddr string, qconn quic.EarlyConnection, +func (t *Trace) OnQUICHandshakeDone(started time.Time, remoteAddr string, qconn model.QUICConn, config *tls.Config, err error, finished time.Time) { t.MockOnQUICHandshakeDone(started, remoteAddr, qconn, config, err, finished) } diff --git a/internal/mocks/trace_test.go b/internal/mocks/trace_test.go index 83ee2b4fd3..ca5b229ae1 100644 --- a/internal/mocks/trace_test.go +++ b/internal/mocks/trace_test.go @@ -164,7 +164,7 @@ func TestTrace(t *testing.T) { t.Run("OnQUICHandshakeDone", func(t *testing.T) { var called bool tx := &Trace{ - MockOnQUICHandshakeDone: func(started time.Time, remoteAddr string, qconn quic.EarlyConnection, config *tls.Config, err error, finished time.Time) { + MockOnQUICHandshakeDone: func(started time.Time, remoteAddr string, qconn model.QUICConn, config *tls.Config, err error, finished time.Time) { called = true }, } diff --git a/internal/model/netx.go b/internal/model/netx.go index a0436a2bc9..a302d87c8e 100644 --- a/internal/model/netx.go +++ b/internal/model/netx.go @@ -246,6 +246,33 @@ type QUICDialerWrapper interface { WrapQUICDialer(qd QUICDialer) QUICDialer } +// QUICConn is the interface representing a *quic.Conn compatible QUIC +// connection. +type QUICConn interface { + // CloseWithError closes the connection using the given application + // error code and reason string. + CloseWithError(code quic.ApplicationErrorCode, reason string) error + + // HandshakeComplete returns a channel that is closed when the QUIC + // handshake completes. + HandshakeComplete() <-chan struct{} + + // ConnectionState returns the state of the QUIC connection. + ConnectionState() quic.ConnectionState + + // LocalAddr returns the local address. + LocalAddr() net.Addr + + // RemoteAddr returns the address of the peer. + RemoteAddr() net.Addr + + // Context returns a context that is cancelled when the connection is closed. + Context() context.Context +} + +// Ensures that a [*quic.Conn] implements the [QUICConn] interface. +var _ QUICConn = &quic.Conn{} + // QUICDialer dials QUIC sessions. type QUICDialer interface { // DialContext establishes a new QUIC session using the given @@ -262,7 +289,7 @@ type QUICDialer interface { // // Typically, you want to pass `&quic.Config{}` as quicConfig. DialContext(ctx context.Context, address string, - tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) + tlsConfig *tls.Config, quicConfig *quic.Config) (QUICConn, error) // CloseIdleConnections closes idle connections, if any. CloseIdleConnections() @@ -522,7 +549,7 @@ type Trace interface { // consist of an IP address and a port (e.g., 8.8.8.8:443, [::1]:5421); // // - qconn is the QUIC connection we receive after the handshake: either - // a valid quic.EarlyConnection or nil; + // a valid QUICConn or nil; // // - config is the non-nil TLS config we are using; // @@ -532,7 +559,7 @@ type Trace interface { // // The error passed to this function will always be wrapped such that the // string returned by Error is an OONI error. - OnQUICHandshakeDone(started time.Time, remoteAddr string, qconn quic.EarlyConnection, + OnQUICHandshakeDone(started time.Time, remoteAddr string, qconn QUICConn, config *tls.Config, err error, finished time.Time) } diff --git a/internal/netxlite/http3.go b/internal/netxlite/http3.go index 6ca80772b2..6988c8e1f2 100644 --- a/internal/netxlite/http3.go +++ b/internal/netxlite/http3.go @@ -5,11 +5,13 @@ package netxlite // import ( + "context" "crypto/tls" "io" "net/http" "github.com/ooni/probe-cli/v3/internal/model" + "github.com/quic-go/quic-go" "github.com/quic-go/quic-go/http3" ) @@ -43,14 +45,30 @@ func (txp *http3Transport) CloseIdleConnections() { txp.dialer.CloseIdleConnections() } +// quicConnForHTTP3 unwraps a [model.QUICConn] to the concrete *quic.Conn required +// by http3.Transport.Dial. +func quicConnForHTTP3(qconn model.QUICConn) *quic.Conn { + if u, ok := qconn.(quicConnUnwrapper); ok { + return u.unwrapForHTTP3() + } + return qconn.(*quic.Conn) +} + // NewHTTP3Transport creates a new HTTPTransport using http3. The // dialer argument MUST NOT be nil. If the tlsConfig argument is nil, // then the code will use the default TLS configuration. func NewHTTP3Transport( logger model.DebugLogger, dialer model.QUICDialer, tlsConfig *tls.Config) model.HTTPTransport { return WrapHTTPTransport(logger, &http3Transport{ - child: &http3.RoundTripper{ - Dial: dialer.DialContext, + child: &http3.Transport{ + Dial: func(ctx context.Context, addr string, + tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) { + qconn, err := dialer.DialContext(ctx, addr, tlsCfg, cfg) + if err != nil { + return nil, err + } + return quicConnForHTTP3(qconn), nil + }, // The following (1) reduces the number of headers that Go will // automatically send for us and (2) ensures that we always receive // back the true headers, such as Content-Length. This change is diff --git a/internal/netxlite/http3_test.go b/internal/netxlite/http3_test.go index 9374dfee61..7f91b8fa48 100644 --- a/internal/netxlite/http3_test.go +++ b/internal/netxlite/http3_test.go @@ -102,7 +102,7 @@ func verifyTypeChainForHTTP3(t *testing.T, txp model.HTTPTransport, t.Fatal("invalid resolver") } } - h3 := h3txp.child.(*http3.RoundTripper) + h3 := h3txp.child.(*http3.Transport) if h3.Dial == nil { t.Fatal("invalid Dial") } diff --git a/internal/netxlite/quic.go b/internal/netxlite/quic.go index d970cffdbc..17485f9214 100644 --- a/internal/netxlite/quic.go +++ b/internal/netxlite/quic.go @@ -76,7 +76,7 @@ type quicDialerQUICGo struct { // mockDialEarly allows to mock quic.DialEarly. mockDialEarly func(ctx context.Context, pconn net.PacketConn, remoteAddr net.Addr, tlsConfig *tls.Config, - quicConfig *quic.Config) (quic.EarlyConnection, error) + quicConfig *quic.Config) (model.QUICConn, error) // provider is the OPTIONAL nil-safe [model.UnderlyingNetwork] provider. provider *MaybeCustomUnderlyingNetwork @@ -120,7 +120,7 @@ func ParseUDPAddr(address string) (*net.UDPAddr, error) { // then we configure, respectively, "h3" and "dq". func (d *quicDialerQUICGo) DialContext(ctx context.Context, address string, tlsConfig *tls.Config, quicConfig *quic.Config) ( - quic.EarlyConnection, error) { + model.QUICConn, error) { udpAddr, err := ParseUDPAddr(address) if err != nil { return nil, err @@ -147,13 +147,16 @@ func (d *quicDialerQUICGo) DialContext(ctx context.Context, func (d *quicDialerQUICGo) dialEarly(ctx context.Context, pconn net.PacketConn, remoteAddr net.Addr, - tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) { + tlsConfig *tls.Config, quicConfig *quic.Config) (model.QUICConn, error) { if d.mockDialEarly != nil { return d.mockDialEarly( ctx, pconn, remoteAddr, tlsConfig, quicConfig) } - return quic.DialEarly( - ctx, pconn, remoteAddr, tlsConfig, quicConfig) + qconn, err := quic.DialEarly(ctx, pconn, remoteAddr, tlsConfig, quicConfig) + if err != nil { + return nil, err + } + return qconn, nil } // maybeApplyTLSDefaults ensures that we're using our certificate pool, if @@ -191,7 +194,7 @@ var _ model.QUICDialer = &quicDialerHandshakeCompleter{} // DialContext implements model.QUICDialer.DialContext. func (d *quicDialerHandshakeCompleter) DialContext( ctx context.Context, address string, - tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) { + tlsConfig *tls.Config, quicConfig *quic.Config) (model.QUICConn, error) { conn, err := d.Dialer.DialContext(ctx, address, tlsConfig, quicConfig) if err != nil { return nil, err @@ -212,25 +215,47 @@ func (d *quicDialerHandshakeCompleter) CloseIdleConnections() { // quicConnectionOwnsConn ensures that we close the UDPLikeConn. type quicConnectionOwnsConn struct { - // EarlyConnection is the embedded early connection - quic.EarlyConnection + // QUICConn is the embedded QUIC connection. + model.QUICConn // conn is the connection we own conn model.UDPLikeConn } -func newQUICConnectionOwnsConn(qconn quic.EarlyConnection, pconn model.UDPLikeConn) *quicConnectionOwnsConn { - return &quicConnectionOwnsConn{EarlyConnection: qconn, conn: pconn} +func newQUICConnectionOwnsConn(qconn model.QUICConn, pconn model.UDPLikeConn) *quicConnectionOwnsConn { + return &quicConnectionOwnsConn{QUICConn: qconn, conn: pconn} } -// CloseWithError implements quic.EarlyConnection.CloseWithError. +// CloseWithError implements model.QUICConn.CloseWithError. func (qconn *quicConnectionOwnsConn) CloseWithError( code quic.ApplicationErrorCode, reason string) error { - err := qconn.EarlyConnection.CloseWithError(code, reason) + err := qconn.QUICConn.CloseWithError(code, reason) _ = qconn.conn.Close() return err } +// quicConnUnwrapper is implemented by QUIC connections wrapping a concrete +// *quic.Conn. It exists because http3.Transport.Dial requires a concrete +// *quic.Conn while [model.QUICDialer] returns the mockable [model.QUICConn]. +type quicConnUnwrapper interface { + unwrapForHTTP3() *quic.Conn +} + +var _ quicConnUnwrapper = &quicConnectionOwnsConn{} + +// unwrapForHTTP3 returns the underlying *quic.Conn for use by http3. Because +// http3 then holds the raw *quic.Conn and closes it via *quic.Conn.CloseWithError +// (bypassing our owning override), we tie closing the owned UDPLikeConn to the +// connection's context, which is cancelled when the connection is closed. +func (qconn *quicConnectionOwnsConn) unwrapForHTTP3() *quic.Conn { + conn := qconn.QUICConn.(*quic.Conn) // the real dialer always wraps a *quic.Conn + go func() { + <-conn.Context().Done() + _ = qconn.conn.Close() + }() + return conn +} + // quicDialerResolver is a dialer that uses the configured Resolver // to resolve a domain name to IP addrs. type quicDialerResolver struct { @@ -250,7 +275,7 @@ var _ model.QUICDialer = &quicDialerResolver{} // contained inside of the `address` endpoint. func (d *quicDialerResolver) DialContext( ctx context.Context, address string, - tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) { + tlsConfig *tls.Config, quicConfig *quic.Config) (model.QUICConn, error) { onlyhost, onlyport, err := net.SplitHostPort(address) if err != nil { return nil, err @@ -322,7 +347,7 @@ var _ model.QUICDialer = &quicDialerLogger{} // DialContext implements QUICContextDialer.DialContext. func (d *quicDialerLogger) DialContext( ctx context.Context, address string, - tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) { + tlsConfig *tls.Config, quicConfig *quic.Config) (model.QUICConn, error) { d.Logger.Debugf("quic_dial%s %s/udp...", d.operationSuffix, address) qconn, err := d.Dialer.DialContext(ctx, address, tlsConfig, quicConfig) if err != nil { @@ -340,14 +365,14 @@ func (d *quicDialerLogger) CloseIdleConnections() { } // NewSingleUseQUICDialer is like NewSingleUseDialer but for QUIC. -func NewSingleUseQUICDialer(qconn quic.EarlyConnection) model.QUICDialer { +func NewSingleUseQUICDialer(qconn model.QUICConn) model.QUICDialer { return &quicDialerSingleUse{qconn: qconn} } // quicDialerSingleUse is the QUICDialer returned by NewSingleQUICDialer. type quicDialerSingleUse struct { mu sync.Mutex - qconn quic.EarlyConnection + qconn model.QUICConn } var _ model.QUICDialer = &quicDialerSingleUse{} @@ -355,8 +380,8 @@ var _ model.QUICDialer = &quicDialerSingleUse{} // DialContext implements QUICDialer.DialContext. func (s *quicDialerSingleUse) DialContext( ctx context.Context, addr string, tlsCfg *tls.Config, - cfg *quic.Config) (quic.EarlyConnection, error) { - var qconn quic.EarlyConnection + cfg *quic.Config) (model.QUICConn, error) { + var qconn model.QUICConn defer s.mu.Unlock() s.mu.Lock() if s.qconn == nil { @@ -433,7 +458,7 @@ var _ model.QUICDialer = &quicDialerErrWrapper{} // DialContext implements ContextDialer.DialContext func (d *quicDialerErrWrapper) DialContext( ctx context.Context, host string, - tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { + tlsCfg *tls.Config, cfg *quic.Config) (model.QUICConn, error) { qconn, err := d.QUICDialer.DialContext(ctx, host, tlsCfg, cfg) if err != nil { return nil, NewErrWrapper( diff --git a/internal/netxlite/quic_test.go b/internal/netxlite/quic_test.go index 1a07967ec2..79997a7077 100644 --- a/internal/netxlite/quic_test.go +++ b/internal/netxlite/quic_test.go @@ -229,7 +229,7 @@ func TestQUICDialerQUICGo(t *testing.T) { UDPListener: &udpListenerStdlib{}, mockDialEarly: func(ctx context.Context, pconn net.PacketConn, remoteAddr net.Addr, tlsConfig *tls.Config, - quicConfig *quic.Config) (quic.EarlyConnection, error) { + quicConfig *quic.Config) (model.QUICConn, error) { gotTLSConfig = tlsConfig return nil, expected }, @@ -270,7 +270,7 @@ func TestQUICDialerQUICGo(t *testing.T) { UDPListener: &udpListenerStdlib{}, mockDialEarly: func(ctx context.Context, pconn net.PacketConn, remoteAddr net.Addr, tlsConfig *tls.Config, - quicConfig *quic.Config) (quic.EarlyConnection, error) { + quicConfig *quic.Config) (model.QUICConn, error) { gotTLSConfig = tlsConfig return nil, expected }, @@ -305,12 +305,12 @@ func TestQUICDialerQUICGo(t *testing.T) { tlsConfig := &tls.Config{ ServerName: "dns.google", } - fakeconn := &mocks.QUICEarlyConnection{} + fakeconn := &mocks.QUICConn{} systemdialer := quicDialerQUICGo{ UDPListener: &udpListenerStdlib{}, mockDialEarly: func(ctx context.Context, pconn net.PacketConn, remoteAddr net.Addr, tlsConfig *tls.Config, - quicConfig *quic.Config) (quic.EarlyConnection, error) { + quicConfig *quic.Config) (model.QUICConn, error) { return fakeconn, nil }, } @@ -321,7 +321,7 @@ func TestQUICDialerQUICGo(t *testing.T) { t.Fatal(err) } connOwner := qconn.(*quicConnectionOwnsConn) - if connOwner.EarlyConnection != fakeconn { + if connOwner.QUICConn != fakeconn { t.Fatal("invalid underlying conn") } }) @@ -373,7 +373,7 @@ func TestQUICDialerWithCustomUnderlyingNetwork(t *testing.T) { systemdialer := &quicDialerQUICGo{ UDPListener: &udpListenerStdlib{}, mockDialEarly: func(ctx context.Context, pconn net.PacketConn, remoteAddr net.Addr, - tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) { + tlsConfig *tls.Config, quicConfig *quic.Config) (model.QUICConn, error) { gotTLSConfig = tlsConfig return nil, expected }, @@ -402,7 +402,7 @@ func TestQUICDialerHandshakeCompleter(t *testing.T) { d := &quicDialerHandshakeCompleter{ Dialer: &mocks.QUICDialer{ MockDialContext: func(ctx context.Context, address string, - tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) { + tlsConfig *tls.Config, quicConfig *quic.Config) (model.QUICConn, error) { return nil, expected }, }, @@ -422,7 +422,7 @@ func TestQUICDialerHandshakeCompleter(t *testing.T) { defer handshakeCancel() ctx, cancel := context.WithCancel(context.Background()) var called bool - expected := &mocks.QUICEarlyConnection{ + expected := &mocks.QUICConn{ MockHandshakeComplete: func() <-chan struct{} { cancel() return handshakeCtx.Done() @@ -435,7 +435,7 @@ func TestQUICDialerHandshakeCompleter(t *testing.T) { d := &quicDialerHandshakeCompleter{ Dialer: &mocks.QUICDialer{ MockDialContext: func(ctx context.Context, address string, - tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) { + tlsConfig *tls.Config, quicConfig *quic.Config) (model.QUICConn, error) { return expected, nil }, }, @@ -455,7 +455,7 @@ func TestQUICDialerHandshakeCompleter(t *testing.T) { t.Run("in case of success", func(t *testing.T) { handshakeCtx, handshakeCancel := context.WithCancel(context.Background()) defer handshakeCancel() - expected := &mocks.QUICEarlyConnection{ + expected := &mocks.QUICConn{ MockHandshakeComplete: func() <-chan struct{} { handshakeCancel() return handshakeCtx.Done() @@ -464,7 +464,7 @@ func TestQUICDialerHandshakeCompleter(t *testing.T) { d := &quicDialerHandshakeCompleter{ Dialer: &mocks.QUICDialer{ MockDialContext: func(ctx context.Context, address string, - tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) { + tlsConfig *tls.Config, quicConfig *quic.Config) (model.QUICConn, error) { return expected, nil }, }, @@ -501,7 +501,7 @@ func TestQUICConnectionOwnsConn(t *testing.T) { quicClose bool udpClose bool ) - qconn := &mocks.QUICEarlyConnection{ + qconn := &mocks.QUICConn{ MockCloseWithError: func(code quic.ApplicationErrorCode, reason string) error { quicClose = true return nil @@ -617,7 +617,7 @@ func TestQUICDialerResolver(t *testing.T) { Resolver: netx.NewStdlibResolver(log.Log), Dialer: &mocks.QUICDialer{ MockDialContext: func(ctx context.Context, address string, - tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) { + tlsConfig *tls.Config, quicConfig *quic.Config) (model.QUICConn, error) { gotTLSConfig = tlsConfig return nil, expected }, @@ -640,13 +640,13 @@ func TestQUICDialerResolver(t *testing.T) { }) t.Run("on success", func(t *testing.T) { - expectedQConn := &mocks.QUICEarlyConnection{} + expectedQConn := &mocks.QUICConn{} netx := &Netx{} dialer := &quicDialerResolver{ Resolver: netx.NewStdlibResolver(log.Log), Dialer: &mocks.QUICDialer{ MockDialContext: func(ctx context.Context, address string, - tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) { + tlsConfig *tls.Config, quicConfig *quic.Config) (model.QUICConn, error) { return expectedQConn, nil }, }} @@ -708,8 +708,8 @@ func TestQUICLoggerDialer(t *testing.T) { Dialer: &mocks.QUICDialer{ MockDialContext: func(ctx context.Context, address string, tlsConfig *tls.Config, - quicConfig *quic.Config) (quic.EarlyConnection, error) { - return &mocks.QUICEarlyConnection{ + quicConfig *quic.Config) (model.QUICConn, error) { + return &mocks.QUICConn{ MockCloseWithError: func( code quic.ApplicationErrorCode, reason string) error { return nil @@ -746,7 +746,7 @@ func TestQUICLoggerDialer(t *testing.T) { Dialer: &mocks.QUICDialer{ MockDialContext: func(ctx context.Context, address string, tlsConfig *tls.Config, - quicConfig *quic.Config) (quic.EarlyConnection, error) { + quicConfig *quic.Config) (model.QUICConn, error) { return nil, expected }, }, @@ -770,7 +770,7 @@ func TestQUICLoggerDialer(t *testing.T) { } func TestNewSingleUseQUICDialer(t *testing.T) { - qconn := &mocks.QUICEarlyConnection{} + qconn := &mocks.QUICConn{} qd := NewSingleUseQUICDialer(qconn) defer qd.CloseIdleConnections() outconn, err := qd.DialContext( @@ -970,10 +970,10 @@ func TestQUICDialerErrWrapper(t *testing.T) { t.Run("DialContext", func(t *testing.T) { t.Run("on success", func(t *testing.T) { - expectedConn := &mocks.QUICEarlyConnection{} + expectedConn := &mocks.QUICConn{} d := &quicDialerErrWrapper{ QUICDialer: &mocks.QUICDialer{ - MockDialContext: func(ctx context.Context, address string, tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) { + MockDialContext: func(ctx context.Context, address string, tlsConfig *tls.Config, quicConfig *quic.Config) (model.QUICConn, error) { return expectedConn, nil }, }, @@ -992,7 +992,7 @@ func TestQUICDialerErrWrapper(t *testing.T) { expectedErr := io.EOF d := &quicDialerErrWrapper{ QUICDialer: &mocks.QUICDialer{ - MockDialContext: func(ctx context.Context, address string, tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) { + MockDialContext: func(ctx context.Context, address string, tlsConfig *tls.Config, quicConfig *quic.Config) (model.QUICConn, error) { return nil, expectedErr }, }, diff --git a/internal/netxlite/trace.go b/internal/netxlite/trace.go index 7b50913609..4631af55e3 100644 --- a/internal/netxlite/trace.go +++ b/internal/netxlite/trace.go @@ -94,7 +94,7 @@ func (*traceDefault) OnQUICHandshakeStart(now time.Time, remoteAddr string, conf // nothing } -func (*traceDefault) OnQUICHandshakeDone(started time.Time, remoteAddr string, qconn quic.EarlyConnection, +func (*traceDefault) OnQUICHandshakeDone(started time.Time, remoteAddr string, qconn model.QUICConn, config *tls.Config, err error, finished time.Time) { // nothing } diff --git a/internal/tutorial/netxlite/chapter04/main.go b/internal/tutorial/netxlite/chapter04/main.go index 79fa7fa7ea..c5628dd781 100644 --- a/internal/tutorial/netxlite/chapter04/main.go +++ b/internal/tutorial/netxlite/chapter04/main.go @@ -30,6 +30,7 @@ import ( "time" "github.com/apex/log" + "github.com/ooni/probe-cli/v3/internal/model" "github.com/ooni/probe-cli/v3/internal/netxlite" "github.com/quic-go/quic-go" ) @@ -91,7 +92,7 @@ func main() { // // ```Go func dialQUIC(ctx context.Context, address string, - config *tls.Config) (quic.EarlyConnection, tls.ConnectionState, error) { + config *tls.Config) (model.QUICConn, tls.ConnectionState, error) { netx := &netxlite.Netx{} ql := netx.NewUDPListener() d := netx.NewQUICDialerWithoutResolver(ql, log.Log) diff --git a/internal/tutorial/netxlite/chapter08/main.go b/internal/tutorial/netxlite/chapter08/main.go index 59c0b6b020..b2c750d187 100644 --- a/internal/tutorial/netxlite/chapter08/main.go +++ b/internal/tutorial/netxlite/chapter08/main.go @@ -33,6 +33,7 @@ import ( "time" "github.com/apex/log" + "github.com/ooni/probe-cli/v3/internal/model" "github.com/ooni/probe-cli/v3/internal/netxlite" "github.com/quic-go/quic-go" ) @@ -105,7 +106,7 @@ func main() { // ```Go func dialQUIC(ctx context.Context, address string, - config *tls.Config) (quic.EarlyConnection, tls.ConnectionState, error) { + config *tls.Config) (model.QUICConn, tls.ConnectionState, error) { netx := &netxlite.Netx{} ql := netx.NewUDPListener() d := netx.NewQUICDialerWithoutResolver(ql, log.Log) diff --git a/internal/x/dslvm/quic.go b/internal/x/dslvm/quic.go index bdf8bb0e33..9f12e870b2 100644 --- a/internal/x/dslvm/quic.go +++ b/internal/x/dslvm/quic.go @@ -41,7 +41,7 @@ type QUICHandshakeStage struct { // QUICConnection is a QUIC connection. type QUICConnection struct { - Conn quic.EarlyConnection + Conn model.QUICConn tlsConfig *tls.Config tx Trace } diff --git a/internal/x/dslx/http_test.go b/internal/x/dslx/http_test.go index 4b83575ecb..563953f3b1 100644 --- a/internal/x/dslx/http_test.go +++ b/internal/x/dslx/http_test.go @@ -448,7 +448,7 @@ func TestHTTPQUIC(t *testing.T) { }) t.Run("Apply httpTransportQUICFunc", func(t *testing.T) { - conn := &mocks.QUICEarlyConnection{} + conn := &mocks.QUICConn{} idGen := &atomic.Int64{} zeroTime := time.Time{} trace := measurexlite.NewTrace(idGen.Add(1), zeroTime) diff --git a/internal/x/dslx/quic.go b/internal/x/dslx/quic.go index 6776a77ec4..e9155f5891 100644 --- a/internal/x/dslx/quic.go +++ b/internal/x/dslx/quic.go @@ -11,6 +11,7 @@ import ( "time" "github.com/ooni/probe-cli/v3/internal/logx" + "github.com/ooni/probe-cli/v3/internal/model" "github.com/quic-go/quic-go" ) @@ -89,7 +90,7 @@ type QUICConnection struct { Address string // QUICConn is the established QUIC conn. - QUICConn quic.EarlyConnection + QUICConn model.QUICConn // Domain is the OPTIONAL domain we resolved. Domain string @@ -109,7 +110,7 @@ type QUICConnection struct { } type quicCloserConn struct { - quic.EarlyConnection + model.QUICConn } func (c *quicCloserConn) Close() error { diff --git a/internal/x/dslx/quic_test.go b/internal/x/dslx/quic_test.go index d2f4fd97ef..0d443203b7 100644 --- a/internal/x/dslx/quic_test.go +++ b/internal/x/dslx/quic_test.go @@ -24,7 +24,7 @@ Test cases: func TestQUICHandshake(t *testing.T) { t.Run("Apply quicHandshakeFunc", func(t *testing.T) { wasClosed := false - plainConn := &mocks.QUICEarlyConnection{ + plainConn := &mocks.QUICConn{ MockCloseWithError: func(code quic.ApplicationErrorCode, reason string) error { wasClosed = true return nil @@ -36,14 +36,14 @@ func TestQUICHandshake(t *testing.T) { eofDialer := &mocks.QUICDialer{ MockDialContext: func(ctx context.Context, address string, tlsConfig *tls.Config, - quicConfig *quic.Config) (quic.EarlyConnection, error) { + quicConfig *quic.Config) (model.QUICConn, error) { return nil, io.EOF }, } goodDialer := &mocks.QUICDialer{ MockDialContext: func(ctx context.Context, address string, tlsConfig *tls.Config, - quicConfig *quic.Config) (quic.EarlyConnection, error) { + quicConfig *quic.Config) (model.QUICConn, error) { return plainConn, nil }, } @@ -52,7 +52,7 @@ func TestQUICHandshake(t *testing.T) { dialer model.QUICDialer sni string tags []string - expectConn quic.EarlyConnection + expectConn model.QUICConn expectErr error closed bool }{ diff --git a/internal/x/dslx/runtimeminimal_test.go b/internal/x/dslx/runtimeminimal_test.go index f773ccf82d..20d69885fe 100644 --- a/internal/x/dslx/runtimeminimal_test.go +++ b/internal/x/dslx/runtimeminimal_test.go @@ -34,7 +34,7 @@ func closeableConnWithErr(err error) io.Closer { func closeableQUICConnWithErr(err error) io.Closer { return &quicCloserConn{ - &mocks.QUICEarlyConnection{ + &mocks.QUICConn{ MockCloseWithError: func(code quic.ApplicationErrorCode, reason string) error { return err }, From badef7e99b0b83aeca10161339e1eea81bb5df78 Mon Sep 17 00:00:00 2001 From: decfox Date: Thu, 30 Jul 2026 14:30:42 +0530 Subject: [PATCH 2/2] fix: pass in tlsconfig copy to avoid race --- internal/netxlite/netx_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/netxlite/netx_test.go b/internal/netxlite/netx_test.go index 1800a7492b..e28f214fb0 100644 --- a/internal/netxlite/netx_test.go +++ b/internal/netxlite/netx_test.go @@ -54,7 +54,7 @@ func TestNetxWithNetem(t *testing.T) { webServerTCPListener := runtimex.Try1(webServerStack.ListenTCP("tcp", webServerTCPAddress)) webServerTCPServer := &http.Server{ Handler: webServerHandler, - TLSConfig: webServerTLSConfig, + TLSConfig: webServerTLSConfig.Clone(), } go webServerTCPServer.ServeTLS(webServerTCPListener, "", "") defer webServerTCPServer.Close() @@ -67,7 +67,7 @@ func TestNetxWithNetem(t *testing.T) { } webServerUDPListener := runtimex.Try1(webServerStack.ListenUDP("udp", webServerUDPAddress)) webServerUDPServer := &http3.Server{ - TLSConfig: webServerTLSConfig, + TLSConfig: webServerTLSConfig.Clone(), QUICConfig: &quic.Config{}, Handler: webServerHandler, }