Repository: quic-go/quic-go
Pull request #4507 (2024-05-13 05:15:01)

introduce Transport.ConnContext, use client's context on the connection
For #4422.

TODO:
- [ ] add documentation for quic-go.net

This will allow us to remote the `ConnectionTracingID`.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/quic-go/quic-go/pull/4507?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go) Report
Attention: Patch coverage is `66.66667%` with `6 lines` in your changes are missing coverage. Please review.
> Project coverage is 85.08%. Comparing base [(`508b402`)](https://app.codecov.io/gh/quic-go/quic-go/commit/508b402a19626beac13916566b4a6fbde1b8fdba?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go) to head [(`60d4e96`)](https://app.codecov.io/gh/quic-go/quic-go/pull/4507?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go).

| [Files](https://app.codecov.io/gh/quic-go/quic-go/pull/4507?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go) | Patch % | Lines |
|---|---|---|
| [server.go](https://app.codecov.io/gh/quic-go/quic-go/pull/4507?src=pr&el=tree&filepath=server.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go#diff-c2VydmVyLmdv) | 54.55% | [4 Missing and 1 partial :warning: ](https://app.codecov.io/gh/quic-go/quic-go/pull/4507?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go) |
| [connection.go](https://app.codecov.io/gh/quic-go/quic-go/pull/4507?src=pr&el=tree&filepath=connection.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go#diff-Y29ubmVjdGlvbi5nbw==) | 66.67% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/quic-go/quic-go/pull/4507?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #4507      +/-   ##
==========================================
- Coverage   85.16%   85.08%   -0.08%     
==========================================
  Files         154      151       -3     
  Lines       14768    14499     -269     
==========================================
- Hits        12577    12336     -241     
+ Misses       1686     1674      -12     
+ Partials      505      489      -16     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/quic-go/quic-go/pull/4507?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go).


Patch:
diff --git a/client.go b/client.go
index 20ef7407..1c5654f6 100644
--- a/client.go
+++ b/client.go
@@ -191,6 +191,7 @@ func (c *client) dial(ctx context.Context) error {
 	c.logger.Infof("Starting new connection to %s (%s -> %s), source connection ID %s, destination connection ID %s, version %s", c.tlsConf.ServerName, c.sendConn.LocalAddr(), c.sendConn.RemoteAddr(), c.srcConnID, c.destConnID, c.version)
 
 	c.conn = newClientConnection(
+		context.WithValue(context.WithoutCancel(ctx), ConnectionTracingKey, c.tracingID),
 		c.sendConn,
 		c.packetHandlers,
 		c.destConnID,
@@ -202,7 +203,6 @@ func (c *client) dial(ctx context.Context) error {
 		c.use0RTT,
 		c.hasNegotiatedVersion,
 		c.tracer,
-		c.tracingID,
 		c.logger,
 		c.version,
 	)
diff --git a/connection.go b/connection.go
index 0404fb19..8d832db7 100644
--- a/connection.go
+++ b/connection.go
@@ -52,7 +52,7 @@ type streamManager interface {
 }
 
 type cryptoStreamHandler interface {
-	StartHandshake() error
+	StartHandshake(context.Context) error
 	ChangeConnectionID(protocol.ConnectionID)
 	SetLargest1RTTAcked(protocol.PacketNumber) error
 	SetHandshakeConfirmed()
@@ -222,6 +222,7 @@ var (
 )
 
 var newConnection = func(
+	ctx context.Context,
 	conn sendConn,
 	runner connRunner,
 	origDestConnID protocol.ConnectionID,
@@ -236,7 +237,6 @@ var newConnection = func(
 	tokenGenerator *handshake.TokenGenerator,
 	clientAddressValidated bool,
 	tracer *logging.ConnectionTracer,
-	tracingID ConnectionTracingID,
 	logger utils.Logger,
 	v protocol.Version,
 ) quicConn {
@@ -274,7 +274,7 @@ var newConnection = func(
 		s.queueControlFrame,
 		connIDGenerator,
 	)
-	s.ctx, s.ctxCancel = context.WithCancelCause(context.WithValue(context.Background(), ConnectionTracingKey, tracingID))
+	s.ctx, s.ctxCancel = context.WithCancelCause(ctx)
 	s.preSetup()
 	s.sentPacketHandler, s.receivedPacketHandler = ackhandler.NewAckHandler(
 		0,
@@ -339,6 +339,7 @@ var newConnection = func(
 
 // declare this as a variable, such that we can it mock it in the tests
 var newClientConnection = func(
+	ctx context.Context,
 	conn sendConn,
 	runner connRunner,
 	destConnID protocol.ConnectionID,
@@ -350,7 +351,6 @@ var newClientConnection = func(
 	enable0RTT bool,
 	hasNegotiatedVersion bool,
 	tracer *logging.ConnectionTracer,
-	tracingID ConnectionTracingID,
 	logger utils.Logger,
 	v protocol.Version,
 ) quicConn {
@@ -384,7 +384,7 @@ var newClientConnection = func(
 		s.queueControlFrame,
 		connIDGenerator,
 	)
-	s.ctx, s.ctxCancel = context.WithCancelCause(context.WithValue(context.Background(), ConnectionTracingKey, tracingID))
+	s.ctx, s.ctxCancel = context.WithCancelCause(ctx)
 	s.preSetup()
 	s.sentPacketHandler, s.receivedPacketHandler = ackhandler.NewAckHandler(
 		initialPacketNumber,
@@ -506,7 +506,7 @@ func (s *connection) run() error {
 
 	s.timer = *newTimer()
 
-	if err := s.cryptoStreamHandler.StartHandshake(); err != nil {
+	if err := s.cryptoStreamHandler.StartHandshake(s.ctx); err != nil {
 		return err
 	}
 	if err := s.handleHandshakeEvents(); err != nil {
diff --git a/fuzzing/handshake/cmd/corpus.go b/fuzzing/handshake/cmd/corpus.go
index bf7b34a0..25a09766 100644
--- a/fuzzing/handshake/cmd/corpus.go
+++ b/fuzzing/handshake/cmd/corpus.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"context"
 	"crypto/tls"
 	"log"
 	"net"
@@ -49,11 +50,11 @@ func main() {
 		protocol.Version1,
 	)
 
-	if err := client.StartHandshake(); err != nil {
+	if err := client.StartHandshake(context.Background()); err != nil {
 		log.Fatal(err)
 	}
 
-	if err := server.StartHandshake(); err != nil {
+	if err := server.StartHandshake(context.Background()); err != nil {
 		log.Fatal(err)
 	}
 
diff --git a/fuzzing/handshake/fuzz.go b/fuzzing/handshake/fuzz.go
index be34c22d..73849178 100644
--- a/fuzzing/handshake/fuzz.go
+++ b/fuzzing/handshake/fuzz.go
@@ -1,6 +1,7 @@
 package handshake
 
 import (
+	"context"
 	"crypto/rand"
 	"crypto/rsa"
 	"crypto/tls"
@@ -286,7 +287,7 @@ func runHandshake(runConfig [confLen]byte, messageConfig uint8, clientConf *tls.
 		utils.DefaultLogger.WithPrefix("client"),
 		protocol.Version1,
 	)
-	if err := client.StartHandshake(); err != nil {
+	if err := client.StartHandshake(context.Background()); err != nil {
 		log.Fatal(err)
 	}
 	defer client.Close()
@@ -303,7 +304,7 @@ func runHandshake(runConfig [confLen]byte, messageConfig uint8, clientConf *tls.
 		utils.DefaultLogger.WithPrefix("server"),
 		protocol.Version1,
 	)
-	if err := server.StartHandshake(); err != nil {
+	if err := server.StartHandshake(context.Background()); err != nil {
 		log.Fatal(err)
 	}
 	defer server.Close()
diff --git a/internal/handshake/crypto_setup.go b/internal/handshake/crypto_setup.go
index 4273a4a6..2134d114 100644
--- a/internal/handshake/crypto_setup.go
+++ b/internal/handshake/crypto_setup.go
@@ -203,8 +203,8 @@ func (h *cryptoSetup) SetLargest1RTTAcked(pn protocol.PacketNumber) error {
 	return h.aead.SetLargestAcked(pn)
 }
 
-func (h *cryptoSetup) StartHandshake() error {
-	err := h.conn.Start(context.WithValue(context.Background(), QUICVersionContextKey, h.version))
+func (h *cryptoSetup) StartHandshake(ctx context.Context) error {
+	err := h.conn.Start(context.WithValue(ctx, QUICVersionContextKey, h.version))
 	if err != nil {
 		return wrapError(err)
 	}
diff --git a/internal/handshake/interface.go b/internal/handshake/interface.go
index fab224f9..6e0eec77 100644
--- a/internal/handshake/interface.go
+++ b/internal/handshake/interface.go
@@ -1,6 +1,7 @@
 package handshake
 
 import (
+	"context"
 	"crypto/tls"
 	"errors"
 	"io"
@@ -91,7 +92,7 @@ type Event struct {
 
 // CryptoSetup handles the handshake and protecting / unprotecting packets
 type CryptoSetup interface {
-	StartHandshake() error
+	StartHandshake(context.Context) error
 	io.Closer
 	ChangeConnectionID(protocol.ConnectionID)
 	GetSessionTicket() ([]byte, error)
diff --git a/internal/mocks/crypto_setup.go b/internal/mocks/crypto_setup.go
index cb984f64..407d67f2 100644
--- a/internal/mocks/crypto_setup.go
+++ b/internal/mocks/crypto_setup.go
@@ -10,6 +10,7 @@
 package mocks
 
 import (
+	context "context"
 	reflect "reflect"
 
 	handshake "github.com/quic-go/quic-go/internal/handshake"
@@ -690,17 +691,17 @@ func (c *MockCryptoSetupSetLargest1RTTAckedCall) DoAndReturn(f func(protocol.Pac
 }
 
 // StartHandshake mocks base method.
-func (m *MockCryptoSetup) StartHandshake() error {
+func (m *MockCryptoSetup) StartHandshake(arg0 context.Context) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "StartHandshake")
+	ret := m.ctrl.Call(m, "StartHandshake", arg0)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
 // StartHandshake indicates an expected call of StartHandshake.
-func (mr *MockCryptoSetupMockRecorder) StartHandshake() *MockCryptoSetupStartHandshakeCall {
+func (mr *MockCryptoSetupMockRecorder) StartHandshake(arg0 any) *MockCryptoSetupStartHandshakeCall {
 	mr.mock.ctrl.T.Helper()
-	call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartHandshake", reflect.TypeOf((*MockCryptoSetup)(nil).StartHandshake))
+	call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartHandshake", reflect.TypeOf((*MockCryptoSetup)(nil).StartHandshake), arg0)
 	return &MockCryptoSetupStartHandshakeCall{Call: call}
 }
 
@@ -716,13 +717,13 @@ func (c *MockCryptoSetupStartHandshakeCall) Return(arg0 error) *MockCryptoSetupS
 }
 
 // Do rewrite *gomock.Call.Do
-func (c *MockCryptoSetupStartHandshakeCall) Do(f func() error) *MockCryptoSetupStartHandshakeCall {
+func (c *MockCryptoSetupStartHandshakeCall) Do(f func(context.Context) error) *MockCryptoSetupStartHandshakeCall {
 	c.Call = c.Call.Do(f)
 	return c
 }
 
 // DoAndReturn rewrite *gomock.Call.DoAndReturn
-func (c *MockCryptoSetupStartHandshakeCall) DoAndReturn(f func() error) *MockCryptoSetupStartHandshakeCall {
+func (c *MockCryptoSetupStartHandshakeCall) DoAndReturn(f func(context.Context) error) *MockCryptoSetupStartHandshakeCall {
 	c.Call = c.Call.DoAndReturn(f)
 	return c
 }
diff --git a/server.go b/server.go
index 2411714e..29fa047e 100644
--- a/server.go
+++ b/server.go
@@ -76,8 +76,11 @@ type baseServer struct {
 	nextZeroRTTCleanup time.Time
 	zeroRTTQueues      map[protocol.ConnectionID]*zeroRTTQueue // only initialized if acceptEarlyConns == true
 
+	connContext func() context.Context
+
 	// set as a member, so they can be set in the tests
 	newConn func(
+		context.Context,
 		sendConn,
 		connRunner,
 		protocol.ConnectionID, /* original dest connection ID */
@@ -92,7 +95,6 @@ type baseServer struct {
 		*handshake.TokenGenerator,
 		bool, /* client address validated by an address validation token */
 		*logging.ConnectionTracer,
-		ConnectionTracingID,
 		utils.Logger,
 		protocol.Version,
 	) quicConn
@@ -231,6 +233,7 @@ func newServer(
 	conn rawConn,
 	connHandler packetHandlerManager,
 	connIDGenerator ConnectionIDGenerator,
+	connContext func() context.Context,
 	tlsConf *tls.Config,
 	config *Config,
 	tracer *logging.Tracer,
@@ -243,6 +246,7 @@ func newServer(
 ) *baseServer {
 	s := &baseServer{
 		conn:                      conn,
+		connContext:               connContext,
 		tlsConf:                   tlsConf,
 		config:                    config,
 		tokenGenerator:            handshake.NewTokenGenerator(tokenGeneratorKey),
@@ -631,7 +635,16 @@ func (s *baseServer) handleInitialImpl(p receivedPacket, hdr *wire.Header) error
 	}
 
 	var conn quicConn
-	tracingID := nextConnTracingID()
+	var ctx context.Context
+	if s.connContext != nil {
+		ctx = s.connContext()
+		if ctx == nil {
+			panic("quic: ConnContext returned nil")
+		}
+	} else {
+		ctx = context.Background()
+	}
+	ctx = context.WithValue(ctx, ConnectionTracingKey, nextConnTracingID())
 	var tracer *logging.ConnectionTracer
 	if config.Tracer != nil {
 		// Use the same connection ID that is passed to the client's GetLogWriter callback.
@@ -639,7 +652,7 @@ func (s *baseServer) handleInitialImpl(p receivedPacket, hdr *wire.Header) error
 		if origDestConnID.Len() > 0 {
 			connID = origDestConnID
 		}
-		tracer = config.Tracer(context.WithValue(context.Background(), ConnectionTracingKey, tracingID), protocol.PerspectiveServer, connID)
+		tracer = config.Tracer(ctx, protocol.PerspectiveServer, connID)
 	}
 	connID, err := s.connIDGenerator.GenerateConnectionID()
 	if err != nil {
@@ -647,6 +660,7 @@ func (s *baseServer) handleInitialImpl(p receivedPacket, hdr *wire.Header) error
 	}
 	s.logger.Debugf("Changing connection ID to %s.", connID)
 	conn = s.newConn(
+		ctx,
 		newSendConn(s.conn, p.remoteAddr, p.info, s.logger),
 		s.connHandler,
 		origDestConnID,
@@ -661,7 +675,6 @@ func (s *baseServer) handleInitialImpl(p receivedPacket, hdr *wire.Header) error
 		s.tokenGenerator,
 		clientAddrVerified,
 		tracer,
-		tracingID,
 		s.logger,
 		hdr.Version,
 	)
diff --git a/transport.go b/transport.go
index ea219c11..d087d18b 100644
--- a/transport.go
+++ b/transport.go
@@ -89,6 +89,16 @@ type Transport struct {
 	// implementation of this callback (negating its return value).
 	VerifySourceAddress func(net.Addr) bool
 
+	// ConnContext is called when the server accepts a new connection.
+	// The context returned from the callback is used to derive every other context used during the
+	// lifetime of the connection:
+	// * the context passed to crypto/tls (and used on the tls.ClientHelloInfo)
+	// * the context used in Config.Tracer
+	// * the context returned from Connection.Context
+	// * the context returned from SendStream.Context
+	// It is not used for dialed connections.
+	ConnContext func() context.Context
+
 	// A Tracer traces events that don't belong to a single QUIC connection.
 	// Tracer.Close is called when the transport is closed.
 	Tracer *logging.Tracer
@@ -168,6 +178,7 @@ func (t *Transport) createServer(tlsConf *tls.Config, conf *Config, allow0RTT bo
 		t.conn,
 		t.handlerMap,
 		t.connIDGenerator,
+		t.ConnContext,
 		tlsConf,
 		conf,
 		t.Tracer,


Test patch:
diff --git a/client_test.go b/client_test.go
index 5a382e51..98d6e2a8 100644
--- a/client_test.go
+++ b/client_test.go
@@ -33,6 +33,7 @@ var _ = Describe("Client", func() {
 		config          *Config
 
 		originalClientConnConstructor func(
+			ctx context.Context,
 			conn sendConn,
 			runner connRunner,
 			destConnID protocol.ConnectionID,
@@ -44,7 +45,6 @@ var _ = Describe("Client", func() {
 			enable0RTT bool,
 			hasNegotiatedVersion bool,
 			tracer *logging.ConnectionTracer,
-			tracingID ConnectionTracingID,
 			logger utils.Logger,
 			v protocol.Version,
 		) quicConn
@@ -112,6 +112,7 @@ var _ = Describe("Client", func() {
 
 			run := make(chan struct{})
 			newClientConnection = func(
+				_ context.Context,
 				_ sendConn,
 				_ connRunner,
 				_ protocol.ConnectionID,
@@ -123,7 +124,6 @@ var _ = Describe("Client", func() {
 				enable0RTT bool,
 				_ bool,
 				_ *logging.ConnectionTracer,
-				_ ConnectionTracingID,
 				_ utils.Logger,
 				_ protocol.Version,
 			) quicConn {
@@ -149,6 +149,7 @@ var _ = Describe("Client", func() {
 			readyChan := make(chan struct{})
 			done := make(chan struct{})
 			newClientConnection = func(
+				_ context.Context,
 				_ sendConn,
 				runner connRunner,
 				_ protocol.ConnectionID,
@@ -160,7 +161,6 @@ var _ = Describe("Client", func() {
 				enable0RTT bool,
 				_ bool,
 				_ *logging.ConnectionTracer,
-				_ ConnectionTracingID,
 				_ utils.Logger,
 				_ protocol.Version,
 			) quicConn {
@@ -186,6 +186,7 @@ var _ = Describe("Client", func() {
 
 			testErr := errors.New("early handshake error")
 			newClientConnection = func(
+				_ context.Context,
 				_ sendConn,
 				_ connRunner,
 				_ protocol.ConnectionID,
@@ -197,7 +198,6 @@ var _ = Describe("Client", func() {
 				_ bool,
 				_ bool,
 				_ *logging.ConnectionTracer,
-				_ ConnectionTracingID,
 				_ utils.Logger,
 				_ protocol.Version,
 			) quicConn {
@@ -271,6 +271,7 @@ var _ = Describe("Client", func() {
 			var conf *Config
 			done := make(chan struct{})
 			newClientConnection = func(
+				_ context.Context,
 				connP sendConn,
 				_ connRunner,
 				_ protocol.ConnectionID,
@@ -282,7 +283,6 @@ var _ = Describe("Client", func() {
 				_ bool,
 				_ bool,
 				_ *logging.ConnectionTracer,
-				_ ConnectionTracingID,
 				_ utils.Logger,
 				versionP protocol.Version,
 			) quicConn {
@@ -314,6 +314,7 @@ var _ = Describe("Client", func() {
 		It("creates a new connections after version negotiation", func() {
 			var counter int
 			newClientConnection = func(
+				_ context.Context,
 				_ sendConn,
 				runner connRunner,
 				_ protocol.ConnectionID,
@@ -325,7 +326,6 @@ var _ = Describe("Client", func() {
 				_ bool,
 				hasNegotiatedVersion bool,
 				_ *logging.ConnectionTracer,
-				_ ConnectionTracingID,
 				_ utils.Logger,
 				versionP protocol.Version,
 			) quicConn {
diff --git a/connection_test.go b/connection_test.go
index a0af8c36..33af6362 100644
--- a/connection_test.go
+++ b/connection_test.go
@@ -116,6 +116,7 @@ var _ = Describe("Connection", func() {
 		tracer.EXPECT().UpdatedKeyFromTLS(gomock.Any(), gomock.Any()).AnyTimes()
 		tracer.EXPECT().UpdatedCongestionState(gomock.Any())
 		conn = newConnection(
+			context.Background(),
 			mconn,
 			connRunner,
 			protocol.ConnectionID{},
@@ -130,7 +131,6 @@ var _ = Describe("Connection", func() {
 			tokenGenerator,
 			false,
 			tr,
-			1234,
 			utils.DefaultLogger,
 			protocol.Version1,
 		).(*connection)
@@ -364,7 +364,7 @@ var _ = Describe("Connection", func() {
 
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				Expect(conn.run()).To(MatchError(expectedErr))
 			}()
@@ -393,7 +393,7 @@ var _ = Describe("Connection", func() {
 
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				Expect(conn.run()).To(MatchError(testErr))
 			}()
@@ -442,7 +442,7 @@ var _ = Describe("Connection", func() {
 		runConn := func() {
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				runErr <- conn.run()
 			}()
@@ -807,7 +807,7 @@ var _ = Describe("Connection", func() {
 			packer.EXPECT().PackConnectionClose(gomock.Any(), gomock.Any(), conn.version).Return(&coalescedPacket{buffer: getPacketBuffer()}, nil)
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -849,7 +849,7 @@ var _ = Describe("Connection", func() {
 
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -884,7 +884,7 @@ var _ = Describe("Connection", func() {
 
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -910,7 +910,7 @@ var _ = Describe("Connection", func() {
 			done := make(chan struct{})
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				err := conn.run()
 				Expect(err).To(HaveOccurred())
@@ -935,7 +935,7 @@ var _ = Describe("Connection", func() {
 			runErr := make(chan error)
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				runErr <- conn.run()
 			}()
@@ -960,7 +960,7 @@ var _ = Describe("Connection", func() {
 			done := make(chan struct{})
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				err := conn.run()
 				Expect(err).To(HaveOccurred())
@@ -1211,7 +1211,7 @@ var _ = Describe("Connection", func() {
 		runConn := func() {
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 				close(connDone)
@@ -1426,7 +1426,7 @@ var _ = Describe("Connection", func() {
 			})
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -1452,7 +1452,7 @@ var _ = Describe("Connection", func() {
 			})
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -1484,7 +1484,7 @@ var _ = Describe("Connection", func() {
 			})
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -1517,7 +1517,7 @@ var _ = Describe("Connection", func() {
 			})
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -1535,7 +1535,7 @@ var _ = Describe("Connection", func() {
 			sender.EXPECT().Send(gomock.Any(), gomock.Any(), gomock.Any())
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -1554,7 +1554,7 @@ var _ = Describe("Connection", func() {
 			sender.EXPECT().Send(gomock.Any(), gomock.Any(), gomock.Any())
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -1574,7 +1574,7 @@ var _ = Describe("Connection", func() {
 			sender.EXPECT().Send(gomock.Any(), gomock.Any(), gomock.Any())
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -1603,7 +1603,7 @@ var _ = Describe("Connection", func() {
 			sender.EXPECT().Send(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(*packetBuffer, uint16, protocol.ECN) { written <- struct{}{} }).Times(2)
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -1627,7 +1627,7 @@ var _ = Describe("Connection", func() {
 			sender.EXPECT().Send(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(*packetBuffer, uint16, protocol.ECN) { written <- struct{}{} }).Times(3)
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -1646,7 +1646,7 @@ var _ = Describe("Connection", func() {
 				sender.EXPECT().Available().Return(available)
 				go func() {
 					defer GinkgoRecover()
-					cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+					cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 					cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 					conn.run()
 				}()
@@ -1670,7 +1670,7 @@ var _ = Describe("Connection", func() {
 			sender.EXPECT().WouldBlock().AnyTimes()
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -1704,7 +1704,7 @@ var _ = Describe("Connection", func() {
 			sender.EXPECT().Send(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(*packetBuffer, uint16, protocol.ECN) { written <- struct{}{} })
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -1738,7 +1738,7 @@ var _ = Describe("Connection", func() {
 			// don't EXPECT any calls to mconn.Write()
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -1763,7 +1763,7 @@ var _ = Describe("Connection", func() {
 			packer.EXPECT().PackMTUProbePacket(ping, protocol.ByteCount(1234), conn.version).Return(shortHeaderPacket{PacketNumber: 1}, getPacketBuffer(), nil)
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -1812,7 +1812,7 @@ var _ = Describe("Connection", func() {
 
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -1846,7 +1846,7 @@ var _ = Describe("Connection", func() {
 			tracer.EXPECT().SentShortHeaderPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -1904,7 +1904,7 @@ var _ = Describe("Connection", func() {
 
 		go func() {
 			defer GinkgoRecover()
-			cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+			cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 			cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 			conn.run()
 		}()
@@ -1986,7 +1986,7 @@ var _ = Describe("Connection", func() {
 		tracer.EXPECT().Close()
 		go func() {
 			defer GinkgoRecover()
-			cryptoSetup.EXPECT().StartHandshake()
+			cryptoSetup.EXPECT().StartHandshake(gomock.Any())
 			cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 			conn.run()
 		}()
@@ -2024,7 +2024,7 @@ var _ = Describe("Connection", func() {
 		sph.EXPECT().DropPackets(protocol.EncryptionHandshake)
 		go func() {
 			defer GinkgoRecover()
-			cryptoSetup.EXPECT().StartHandshake()
+			cryptoSetup.EXPECT().StartHandshake(gomock.Any())
 			cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventHandshakeComplete})
 			cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 			cryptoSetup.EXPECT().SetHandshakeConfirmed()
@@ -2050,7 +2050,7 @@ var _ = Describe("Connection", func() {
 		done := make(chan struct{})
 		go func() {
 			defer GinkgoRecover()
-			cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+			cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 			cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 			Expect(conn.run()).To(Succeed())
 			close(done)
@@ -2074,7 +2074,7 @@ var _ = Describe("Connection", func() {
 		done := make(chan struct{})
 		go func() {
 			defer GinkgoRecover()
-			cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+			cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 			cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 			err := conn.run()
 			Expect(err).To(MatchError(expectedErr))
@@ -2125,7 +2125,7 @@ var _ = Describe("Connection", func() {
 		runConn := func() {
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -2228,7 +2228,7 @@ var _ = Describe("Connection", func() {
 			)
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				err := conn.run()
 				nerr, ok := err.(net.Error)
@@ -2254,7 +2254,7 @@ var _ = Describe("Connection", func() {
 			done := make(chan struct{})
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				err := conn.run()
 				nerr, ok := err.(net.Error)
@@ -2288,7 +2288,7 @@ var _ = Describe("Connection", func() {
 			// and not on the last network activity
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -2316,7 +2316,7 @@ var _ = Describe("Connection", func() {
 			conn.handshakeComplete = false
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				cryptoSetup.EXPECT().GetSessionTicket().MaxTimes(1)
 				err := conn.run()
@@ -2349,7 +2349,7 @@ var _ = Describe("Connection", func() {
 			done := make(chan struct{})
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventHandshakeComplete})
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				cryptoSetup.EXPECT().GetSessionTicket().MaxTimes(1)
@@ -2372,7 +2372,7 @@ var _ = Describe("Connection", func() {
 			conn.idleTimeout = 30 * time.Second
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				conn.run()
 			}()
@@ -2404,7 +2404,7 @@ var _ = Describe("Connection", func() {
 			pto := conn.rttStats.PTO(true)
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				cryptoSetup.EXPECT().GetSessionTicket().MaxTimes(1)
 				cryptoSetup.EXPECT().SetHandshakeConfirmed().MaxTimes(1)
@@ -2573,6 +2573,7 @@ var _ = Describe("Client Connection", func() {
 		tracer.EXPECT().UpdatedKeyFromTLS(gomock.Any(), gomock.Any()).AnyTimes()
 		tracer.EXPECT().UpdatedCongestionState(gomock.Any())
 		conn = newClientConnection(
+			context.Background(),
 			mconn,
 			connRunner,
 			destConnID,
@@ -2584,7 +2585,6 @@ var _ = Describe("Client Connection", func() {
 			false,
 			false,
 			tr,
-			1234,
 			utils.DefaultLogger,
 			protocol.Version1,
 		).(*connection)
@@ -2625,7 +2625,7 @@ var _ = Describe("Client Connection", func() {
 		Expect(conn.handlePacketImpl(p)).To(BeTrue())
 		go func() {
 			defer GinkgoRecover()
-			cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+			cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 			cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 			conn.run()
 		}()
@@ -2699,7 +2699,7 @@ var _ = Describe("Client Connection", func() {
 		tracer.EXPECT().ClosedConnection(gomock.Any())
 		tracer.EXPECT().Close()
 		running := make(chan struct{})
-		cryptoSetup.EXPECT().StartHandshake().Do(func() error {
+		cryptoSetup.EXPECT().StartHandshake(gomock.Any()).Do(func(context.Context) error {
 			close(running)
 			conn.closeLocal(errors.New("early error"))
 			return nil
@@ -2756,7 +2756,7 @@ var _ = Describe("Client Connection", func() {
 			start := make(chan struct{})
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().DoAndReturn(func() handshake.Event {
 					<-start
 					return handshake.Event{Kind: handshake.EventNoEvent}
@@ -2787,7 +2787,7 @@ var _ = Describe("Client Connection", func() {
 			packer.EXPECT().PackCoalescedPacket(gomock.Any(), gomock.Any(), gomock.Any()).MaxTimes(1)
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				errChan <- conn.run()
 			}()
@@ -2902,7 +2902,7 @@ var _ = Describe("Client Connection", func() {
 			packer.EXPECT().PackCoalescedPacket(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
 			go func() {
 				defer GinkgoRecover()
-				cryptoSetup.EXPECT().StartHandshake().MaxTimes(1)
+				cryptoSetup.EXPECT().StartHandshake(gomock.Any()).MaxTimes(1)
 				// This is not 100% what would happen in reality.
 				// The run loop calls NextEvent once when it starts up (to send out the ClientHello),
 				// and then again every time a CRYPTO frame is handled.
diff --git a/integrationtests/self/handshake_test.go b/integrationtests/self/handshake_test.go
index 751dc36d..95194aeb 100644
--- a/integrationtests/self/handshake_test.go
+++ b/integrationtests/self/handshake_test.go
@@ -14,6 +14,7 @@ import (
 	"github.com/quic-go/quic-go/internal/protocol"
 	"github.com/quic-go/quic-go/internal/qerr"
 	"github.com/quic-go/quic-go/internal/qtls"
+	"github.com/quic-go/quic-go/logging"
 
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
@@ -122,6 +123,137 @@ var _ = Describe("Handshake tests", func() {
 		Expect(err).To(MatchError("application cancelled"))
 	})
 
+	It("uses the context everywhere, on the server side", func() {
+		//nolint:staticcheck
+		serverCtx := context.WithValue(context.Background(), "foo", "bar")
+		tlsGetConfigForClientContextChan := make(chan context.Context, 1)
+		tlsGetCertificateContextChan := make(chan context.Context, 1)
+		tracerContextChan := make(chan context.Context, 1)
+		connContextChan := make(chan context.Context, 1)
+		streamContextChan := make(chan context.Context, 1)
+		conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
+		Expect(err).ToNot(HaveOccurred())
+		defer conn.Close()
+		tr := &quic.Transport{
+			Conn:        conn,
+			ConnContext: func() context.Context { return serverCtx },
+		}
+		defer tr.Close()
+		server, err := tr.Listen(
+			&tls.Config{
+				GetConfigForClient: func(info *tls.ClientHelloInfo) (*tls.Config, error) {
+					tlsGetConfigForClientContextChan <- info.Context()
+					tlsConf := getTLSConfig()
+					tlsConf.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
+						tlsGetCertificateContextChan <- info.Context()
+						return &tlsConf.Certificates[0], nil
+					}
+					return tlsConf, nil
+				},
+			},
+			getQuicConfig(&quic.Config{
+				Tracer: func(ctx context.Context, _ logging.Perspective, _ quic.ConnectionID) *logging.ConnectionTracer {
+					tracerContextChan <- ctx
+					return nil
+				},
+			}),
+		)
+		Expect(err).ToNot(HaveOccurred())
+		defer server.Close()
+
+		go func() {
+			defer GinkgoRecover()
+			defer close(acceptStopped)
+			for {
+				conn, err := server.Accept(context.Background())
+				if err != nil {
+					return
+				}
+				connContextChan <- conn.Context()
+				str, err := conn.OpenUniStream()
+				Expect(err).ToNot(HaveOccurred())
+				streamContextChan <- str.Context()
+				str.Close()
+			}
+		}()
+
+		c, err := quic.DialAddr(
+			context.Background(),
+			fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
+			getTLSClientConfig(),
+			getQuicConfig(nil),
+		)
+		Expect(err).ToNot(HaveOccurred())
+		defer c.CloseWithError(0, "")
+
+		checkContext := func(c <-chan context.Context) {
+			var ctx context.Context
+			Eventually(c).Should(Receive(&ctx))
+			val := ctx.Value("foo")
+			ExpectWithOffset(1, val).ToNot(BeNil())
+			v := val.(string)
+			ExpectWithOffset(1, v).To(Equal("bar"))
+		}
+		checkContext(connContextChan)
+		checkContext(tlsGetConfigForClientContextChan)
+		checkContext(tlsGetCertificateContextChan)
+		checkContext(tracerContextChan)
+		checkContext(streamContextChan)
+	})
+
+	It("uses the context everywhere, on the client side", func() {
+		tlsServerConf := getTLSConfig()
+		tlsServerConf.ClientAuth = tls.RequestClientCert
+		runServer(tlsServerConf)
+
+		tlsContextChan := make(chan context.Context, 1)
+		tracerContextChan := make(chan context.Context, 1)
+		tlsConf := getTLSClientConfig()
+		tlsConf.GetClientCertificate = func(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
+			tlsContextChan <- info.Context()
+			return &tlsServerConf.Certificates[0], nil
+		}
+		//nolint:staticcheck
+		ctx, cancel := context.WithCancel(context.WithValue(context.Background(), "foo", "bar"))
+		conn, err := quic.DialAddr(
+			ctx,
+			fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
+			tlsConf,
+			getQuicConfig(&quic.Config{
+				Tracer: func(ctx context.Context, _ logging.Perspective, _ quic.ConnectionID) *logging.ConnectionTracer {
+					tracerContextChan <- ctx
+					return nil
+				},
+			}),
+		)
+		Expect(err).ToNot(HaveOccurred())
+		defer conn.CloseWithError(0, "")
+		cancel()
+		// make sure the connection context is not cancelled (even though derived from the ctx passed to Dial)
+		Expect(ctx.Done()).ToNot(Receive())
+
+		checkContext := func(ctx context.Context) {
+			val := ctx.Value("foo")
+			ExpectWithOffset(2, val).ToNot(BeNil())
+			v := val.(string)
+			ExpectWithOffset(2, v).To(Equal("bar"))
+		}
+
+		checkContextFromChan := func(c <-chan context.Context) {
+			var ctx context.Context
+			EventuallyWithOffset(1, c).Should(Receive(&ctx))
+			checkContext(ctx)
+		}
+
+		checkContext(conn.Context())
+		str, err := conn.OpenUniStream()
+		Expect(err).ToNot(HaveOccurred())
+		checkContext(str.Context())
+		str.Close()
+		checkContextFromChan(tlsContextChan)
+		checkContextFromChan(tracerContextChan)
+	})
+
 	Context("using different cipher suites", func() {
 		for n, id := range map[string]uint16{
 			"TLS_AES_128_GCM_SHA256":       tls.TLS_AES_128_GCM_SHA256,
diff --git a/integrationtests/self/self_suite_test.go b/integrationtests/self/self_suite_test.go
index 42c8d944..18698b79 100644
--- a/integrationtests/self/self_suite_test.go
+++ b/integrationtests/self/self_suite_test.go
@@ -186,10 +186,12 @@ func getQuicConfig(conf *quic.Config) *quic.Config {
 	}
 	origTracer := conf.Tracer
 	conf.Tracer = func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer {
-		return logging.NewMultiplexedConnectionTracer(
-			tools.NewQlogConnectionTracer(GinkgoWriter)(ctx, p, connID),
-			origTracer(ctx, p, connID),
-		)
+		tr := origTracer(ctx, p, connID)
+		qlogger := tools.NewQlogConnectionTracer(GinkgoWriter)(ctx, p, connID)
+		if tr == nil {
+			return qlogger
+		}
+		return logging.NewMultiplexedConnectionTracer(qlogger, tr)
 	}
 	return conf
 }
diff --git a/internal/handshake/crypto_setup_test.go b/internal/handshake/crypto_setup_test.go
index a1d58ffa..0054867e 100644
--- a/internal/handshake/crypto_setup_test.go
+++ b/internal/handshake/crypto_setup_test.go
@@ -1,6 +1,7 @@
 package handshake
 
 import (
+	"context"
 	"crypto/rand"
 	"crypto/rsa"
 	"crypto/tls"
@@ -75,7 +76,7 @@ var _ = Describe("Crypto Setup TLS", func() {
 			protocol.Version1,
 		)
 
-		Expect(cl.StartHandshake()).To(MatchError(&qerr.TransportError{
+		Expect(cl.StartHandshake(context.Background())).To(MatchError(&qerr.TransportError{
 			ErrorCode:    qerr.InternalError,
 			ErrorMessage: "tls: invalid NextProtos value",
 		}))
@@ -96,7 +97,7 @@ var _ = Describe("Crypto Setup TLS", func() {
 			protocol.Version1,
 		)
 
-		Expect(server.StartHandshake()).To(Succeed())
+		Expect(server.StartHandshake(context.Background())).To(Succeed())
 
 		fakeCH := append([]byte{typeClientHello, 0, 0, 6}, []byte("foobar")...)
 		// wrong encryption level
@@ -189,8 +190,8 @@ var _ = Describe("Crypto Setup TLS", func() {
 		// The clientEvents and serverEvents contain all events that were not processed by the function,
 		// i.e. not EventWriteInitialData, EventWriteHandshakeData, EventHandshakeComplete.
 		handshake := func(client, server CryptoSetup) (clientEvents []Event, clientErr error, serverEvents []Event, serverErr error) {
-			Expect(client.StartHandshake()).To(Succeed())
-			Expect(server.StartHandshake()).To(Succeed())
+			Expect(client.StartHandshake(context.Background())).To(Succeed())
+			Expect(server.StartHandshake(context.Background())).To(Succeed())
 
 			var clientHandshakeComplete, serverHandshakeComplete bool
 
diff --git a/server_test.go b/server_test.go
index 8012f304..00fa10bd 100644
--- a/server_test.go
+++ b/server_test.go
@@ -287,6 +287,7 @@ var _ = Describe("Server", func() {
 				var newConnID protocol.ConnectionID
 				conn := NewMockQUICConn(mockCtrl)
 				serv.newConn = func(
+					_ context.Context,
 					_ sendConn,
 					_ connRunner,
 					origDestConnID protocol.ConnectionID,
@@ -301,7 +302,6 @@ var _ = Describe("Server", func() {
 					_ *handshake.TokenGenerator,
 					_ bool,
 					_ *logging.ConnectionTracer,
-					_ ConnectionTracingID,
 					_ utils.Logger,
 					_ protocol.Version,
 				) quicConn {
@@ -489,6 +489,7 @@ var _ = Describe("Server", func() {
 				var newConnID protocol.ConnectionID
 				conn := NewMockQUICConn(mockCtrl)
 				serv.newConn = func(
+					_ context.Context,
 					_ sendConn,
 					_ connRunner,
 					origDestConnID protocol.ConnectionID,
@@ -503,7 +504,6 @@ var _ = Describe("Server", func() {
 					_ *handshake.TokenGenerator,
 					_ bool,
 					_ *logging.ConnectionTracer,
-					_ ConnectionTracingID,
 					_ utils.Logger,
 					_ protocol.Version,
 				) quicConn {
@@ -557,6 +557,7 @@ var _ = Describe("Server", func() {
 				acceptConn := make(chan struct{})
 				var counter atomic.Uint32
 				serv.newConn = func(
+					_ context.Context,
 					_ sendConn,
 					runner connRunner,
 					_ protocol.ConnectionID,
@@ -571,7 +572,6 @@ var _ = Describe("Server", func() {
 					_ *handshake.TokenGenerator,
 					_ bool,
 					_ *logging.ConnectionTracer,
-					_ ConnectionTracingID,
 					_ utils.Logger,
 					_ protocol.Version,
 				) quicConn {
@@ -612,6 +612,7 @@ var _ = Describe("Server", func() {
 			It("only creates a single connection for a duplicate Initial", func() {
 				done := make(chan struct{})
 				serv.newConn = func(
+					_ context.Context,
 					_ sendConn,
 					runner connRunner,
 					_ protocol.ConnectionID,
@@ -626,7 +627,6 @@ var _ = Describe("Server", func() {
 					_ *handshake.TokenGenerator,
 					_ bool,
 					_ *logging.ConnectionTracer,
-					_ ConnectionTracingID,
 					_ utils.Logger,
 					_ protocol.Version,
 				) quicConn {
@@ -661,6 +661,7 @@ var _ = Describe("Server", func() {
 				wg.Add(limit)
 				done := make(chan struct{})
 				serv.newConn = func(
+					_ context.Context,
 					_ sendConn,
 					runner connRunner,
 					_ protocol.ConnectionID,
@@ -675,7 +676,6 @@ var _ = Describe("Server", func() {
 					_ *handshake.TokenGenerator,
 					_ bool,
 					_ *logging.ConnectionTracer,
-					_ ConnectionTracingID,
 					_ utils.Logger,
 					_ protocol.Version,
 				) quicConn {
@@ -722,6 +722,7 @@ var _ = Describe("Server", func() {
 		Context("token validation", func() {
 			It("decodes the token from the token field", func() {
 				serv.newConn = func(
+					_ context.Context,
 					_ sendConn,
 					_ connRunner,
 					_ protocol.ConnectionID,
@@ -736,7 +737,6 @@ var _ = Describe("Server", func() {
 					_ *handshake.TokenGenerator,
 					_ bool,
 					_ *logging.ConnectionTracer,
-					_ ConnectionTracingID,
 					_ utils.Logger,
 					_ protocol.Version,
 				) quicConn {
@@ -955,6 +955,7 @@ var _ = Describe("Server", func() {
 
 				destroyed := make(chan struct{})
 				serv.newConn = func(
+					_ context.Context,
 					_ sendConn,
 					_ connRunner,
 					_ protocol.ConnectionID,
@@ -969,7 +970,6 @@ var _ = Describe("Server", func() {
 					_ *handshake.TokenGenerator,
 					_ bool,
 					_ *logging.ConnectionTracer,
-					_ ConnectionTracingID,
 					_ utils.Logger,
 					_ protocol.Version,
 				) quicConn {
@@ -1022,6 +1022,7 @@ var _ = Describe("Server", func() {
 
 				handshakeChan := make(chan struct{})
 				serv.newConn = func(
+					_ context.Context,
 					_ sendConn,
 					_ connRunner,
 					_ protocol.ConnectionID,
@@ -1036,7 +1037,6 @@ var _ = Describe("Server", func() {
 					_ *handshake.TokenGenerator,
 					_ bool,
 					_ *logging.ConnectionTracer,
-					_ ConnectionTracingID,
 					_ utils.Logger,
 					_ protocol.Version,
 				) quicConn {
@@ -1092,6 +1092,7 @@ var _ = Describe("Server", func() {
 
 				handshakeChan := make(chan struct{})
 				serv.newConn = func(
+					_ context.Context,
 					_ sendConn,
 					runner connRunner,
 					_ protocol.ConnectionID,
@@ -1106,7 +1107,6 @@ var _ = Describe("Server", func() {
 					_ *handshake.TokenGenerator,
 					_ bool,
 					_ *logging.ConnectionTracer,
-					_ ConnectionTracingID,
 					_ utils.Logger,
 					_ protocol.Version,
 				) quicConn {
@@ -1162,6 +1162,7 @@ var _ = Describe("Server", func() {
 
 			ready := make(chan struct{})
 			serv.baseServer.newConn = func(
+				_ context.Context,
 				_ sendConn,
 				runner connRunner,
 				_ protocol.ConnectionID,
@@ -1176,7 +1177,6 @@ var _ = Describe("Server", func() {
 				_ *handshake.TokenGenerator,
 				_ bool,
 				_ *logging.ConnectionTracer,
-				_ ConnectionTracingID,
 				_ utils.Logger,
 				_ protocol.Version,
 			) quicConn {
@@ -1203,6 +1203,7 @@ var _ = Describe("Server", func() {
 			var wg sync.WaitGroup // to make sure the test fully completes
 			wg.Add(protocol.MaxAcceptQueueSize)
 			serv.baseServer.newConn = func(
+				_ context.Context,
 				_ sendConn,
 				runner connRunner,
 				_ protocol.ConnectionID,
@@ -1217,7 +1218,6 @@ var _ = Describe("Server", func() {
 				_ *handshake.TokenGenerator,
 				_ bool,
 				_ *logging.ConnectionTracer,
-				_ ConnectionTracingID,
 				_ utils.Logger,
 				_ protocol.Version,
 			) quicConn {
@@ -1262,6 +1262,7 @@ var _ = Describe("Server", func() {
 			connCreated := make(chan struct{})
 			conn := NewMockQUICConn(mockCtrl)
 			serv.baseServer.newConn = func(
+				_ context.Context,
 				_ sendConn,
 				runner connRunner,
 				_ protocol.ConnectionID,
@@ -1276,7 +1277,6 @@ var _ = Describe("Server", func() {
 				_ *handshake.TokenGenerator,
 				_ bool,
 				_ *logging.ConnectionTracer,
-				_ ConnectionTracingID,
 				_ utils.Logger,
 				_ protocol.Version,
 			) quicConn {
@@ -1384,6 +1384,7 @@ var _ = Describe("Server", func() {
 			}, make([]byte, protocol.MinInitialPacketSize))
 			called := make(chan struct{})
 			serv.newConn = func(
+				_ context.Context,
 				_ sendConn,
 				_ connRunner,
 				_ protocol.ConnectionID,
@@ -1398,7 +1399,6 @@ var _ = Describe("Server", func() {
 				_ *handshake.TokenGenerator,
 				_ bool,
 				_ *logging.ConnectionTracer,
-				_ ConnectionTracingID,
 				_ utils.Logger,
 				_ protocol.Version,
 			) quicConn {