Repository: quic-go/quic-go
Pull request #3991 (2023-07-22 22:30:45)

wire: always set the QUIC bit for Version Negotiation packets
Let's wait for a conclusion of the mailing list discussion before merging this: https://mailarchive.ietf.org/arch/msg/quic/oR4kxGKY6mjtPC1CZegY1ED4beg/


Patch:
diff --git a/.github/workflows/cross-compile.yml b/.github/workflows/cross-compile.yml
index 042953f8..e0855821 100644
--- a/.github/workflows/cross-compile.yml
+++ b/.github/workflows/cross-compile.yml
@@ -4,7 +4,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        go: [ "1.20.x", "1.21.0-rc.3" ]
+        go: [ "1.20.x", "1.21.0-rc.2" ]
     runs-on: ${{ fromJSON(vars['CROSS_COMPILE_RUNNER_UBUNTU'] || '"ubuntu-latest"') }}
     name: "Cross Compilation (Go ${{matrix.go}})"
     steps:
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 362a24ec..b6e33e78 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -5,7 +5,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        go: [ "1.20.x", "1.21.0-rc.3" ]
+        go: [ "1.20.x", "1.21.0-rc.2" ]
     runs-on: ${{ fromJSON(vars['INTEGRATION_RUNNER_UBUNTU'] || '"ubuntu-latest"') }}
     env:
       DEBUG: false # set this to true to export qlogs and save them as artifacts
diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml
index fb21749f..f436d748 100644
--- a/.github/workflows/unit.yml
+++ b/.github/workflows/unit.yml
@@ -7,7 +7,7 @@ jobs:
       fail-fast: false
       matrix:
         os: [ "ubuntu", "windows", "macos" ]
-        go: [ "1.20.x", "1.21.0-rc.3" ]
+        go: [ "1.20.x", "1.21.0-rc.2" ]
     runs-on: ${{ fromJSON(vars[format('UNIT_RUNNER_{0}', matrix.os)] || format('"{0}-latest"', matrix.os)) }}
     name: Unit tests (${{ matrix.os}}, Go ${{ matrix.go }})
     steps:
diff --git a/connection.go b/connection.go
index b17f3ce8..bb4d691e 100644
--- a/connection.go
+++ b/connection.go
@@ -315,8 +315,6 @@ var newConnection = func(
 	}
 	cs := handshake.NewCryptoSetupServer(
 		clientDestConnID,
-		conn.LocalAddr(),
-		conn.RemoteAddr(),
 		params,
 		tlsConf,
 		conf.Allow0RTT,
@@ -718,22 +716,20 @@ func (s *connection) idleTimeoutStartTime() time.Time {
 }
 
 func (s *connection) handleHandshakeComplete() error {
+	s.handshakeComplete = true
 	defer s.handshakeCtxCancel()
 	// Once the handshake completes, we have derived 1-RTT keys.
-	// There's no point in queueing undecryptable packets for later decryption anymore.
+	// There's no point in queueing undecryptable packets for later decryption any more.
 	s.undecryptablePackets = nil
 
 	s.connIDManager.SetHandshakeComplete()
 	s.connIDGenerator.SetHandshakeComplete()
 
-	// The server applies transport parameters right away, but the client side has to wait for handshake completion.
-	// During a 0-RTT connection, the client is only allowed to use the new transport parameters for 1-RTT packets.
 	if s.perspective == protocol.PerspectiveClient {
 		s.applyTransportParameters()
 		return nil
 	}
 
-	// All these only apply to the server side.
 	if err := s.handleHandshakeConfirmed(); err != nil {
 		return err
 	}
@@ -1231,7 +1227,6 @@ func (s *connection) handleFrames(
 	if log != nil {
 		frames = make([]logging.Frame, 0, 4)
 	}
-	handshakeWasComplete := s.handshakeComplete
 	var handleErr error
 	for len(data) > 0 {
 		l, frame, err := s.frameParser.ParseNext(data, encLevel, s.version)
@@ -1268,17 +1263,6 @@ func (s *connection) handleFrames(
 			return false, handleErr
 		}
 	}
-
-	// Handle completion of the handshake after processing all the frames.
-	// This ensures that we correctly handle the following case on the server side:
-	// We receive a Handshake packet that contains the CRYPTO frame that allows us to complete the handshake,
-	// and an ACK serialized after that CRYPTO frame. In this case, we still want to process the ACK frame.
-	if !handshakeWasComplete && s.handshakeComplete {
-		if err := s.handleHandshakeComplete(); err != nil {
-			return false, err
-		}
-	}
-
 	return
 }
 
@@ -1374,9 +1358,7 @@ func (s *connection) handleHandshakeEvents() error {
 		case handshake.EventNoEvent:
 			return nil
 		case handshake.EventHandshakeComplete:
-			// Don't call handleHandshakeComplete yet.
-			// It's advantageous to process ACK frames that might be serialized after the CRYPTO frame first.
-			s.handshakeComplete = true
+			err = s.handleHandshakeComplete()
 		case handshake.EventReceivedTransportParameters:
 			err = s.handleTransportParameters(ev.TransportParameters)
 		case handshake.EventRestoredTransportParameters:
@@ -1491,7 +1473,7 @@ func (s *connection) handleHandshakeDoneFrame() error {
 		}
 	}
 	if !s.handshakeConfirmed {
-		return s.handleHandshakeConfirmed()
+		s.handleHandshakeConfirmed()
 	}
 	return nil
 }
@@ -1504,9 +1486,6 @@ func (s *connection) handleAckFrame(frame *wire.AckFrame, encLevel protocol.Encr
 	if !acked1RTTPacket {
 		return nil
 	}
-	// On the client side: If the packet acknowledged a 1-RTT packet, this confirms the handshake.
-	// This is only possible if the ACK was sent in a 1-RTT packet.
-	// This is an optimization over simply waiting for a HANDSHAKE_DONE frame, see section 4.1.2 of RFC 9001.
 	if s.perspective == protocol.PerspectiveClient && !s.handshakeConfirmed {
 		if err := s.handleHandshakeConfirmed(); err != nil {
 			return err
@@ -1678,23 +1657,12 @@ func (s *connection) restoreTransportParameters(params *wire.TransportParameters
 }
 
 func (s *connection) handleTransportParameters(params *wire.TransportParameters) error {
-	if s.tracer != nil {
-		s.tracer.ReceivedTransportParameters(params)
-	}
 	if err := s.checkTransportParameters(params); err != nil {
 		return &qerr.TransportError{
 			ErrorCode:    qerr.TransportParameterError,
 			ErrorMessage: err.Error(),
 		}
 	}
-
-	if s.perspective == protocol.PerspectiveClient && s.peerParams != nil && s.ConnectionState().Used0RTT && !params.ValidForUpdate(s.peerParams) {
-		return &qerr.TransportError{
-			ErrorCode:    qerr.ProtocolViolation,
-			ErrorMessage: "server sent reduced limits after accepting 0-RTT data",
-		}
-	}
-
 	s.peerParams = params
 	// On the client side we have to wait for handshake completion.
 	// During a 0-RTT connection, we are only allowed to use the new transport parameters for 1-RTT packets.
@@ -1715,6 +1683,9 @@ func (s *connection) checkTransportParameters(params *wire.TransportParameters)
 	if s.logger.Debug() {
 		s.logger.Debugf("Processed Transport Parameters: %s", params)
 	}
+	if s.tracer != nil {
+		s.tracer.ReceivedTransportParameters(params)
+	}
 
 	// check the initial_source_connection_id
 	if params.InitialSourceConnectionID != s.handshakeDestConnID {
diff --git a/fuzzing/handshake/cmd/corpus.go b/fuzzing/handshake/cmd/corpus.go
index bf7b34a0..a7e0196f 100644
--- a/fuzzing/handshake/cmd/corpus.go
+++ b/fuzzing/handshake/cmd/corpus.go
@@ -3,7 +3,6 @@ package main
 import (
 	"crypto/tls"
 	"log"
-	"net"
 
 	fuzzhandshake "github.com/quic-go/quic-go/fuzzing/handshake"
 	"github.com/quic-go/quic-go/fuzzing/internal/helper"
@@ -38,8 +37,6 @@ func main() {
 	config.NextProtos = []string{alpn}
 	server := handshake.NewCryptoSetupServer(
 		protocol.ConnectionID{},
-		&net.UDPAddr{IP: net.IPv6loopback, Port: 1234},
-		&net.UDPAddr{IP: net.IPv6loopback, Port: 4321},
 		&wire.TransportParameters{ActiveConnectionIDLimit: 2},
 		config,
 		false,
diff --git a/fuzzing/handshake/fuzz.go b/fuzzing/handshake/fuzz.go
index 2c5c1259..c89f6a9f 100644
--- a/fuzzing/handshake/fuzz.go
+++ b/fuzzing/handshake/fuzz.go
@@ -11,7 +11,6 @@ import (
 	"log"
 	"math"
 	mrand "math/rand"
-	"net"
 	"time"
 
 	"github.com/quic-go/quic-go/fuzzing/internal/helper"
@@ -305,8 +304,6 @@ func runHandshake(runConfig [confLen]byte, messageConfig uint8, clientConf *tls.
 
 	server := handshake.NewCryptoSetupServer(
 		protocol.ConnectionID{},
-		&net.UDPAddr{IP: net.IPv6loopback, Port: 1234},
-		&net.UDPAddr{IP: net.IPv6loopback, Port: 4321},
 		serverTP,
 		serverConf,
 		enable0RTTServer,
diff --git a/go.mod b/go.mod
index 2f2a0ff0..53612c67 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@ require (
 	github.com/onsi/ginkgo/v2 v2.9.5
 	github.com/onsi/gomega v1.27.6
 	github.com/quic-go/qpack v0.4.0
-	github.com/quic-go/qtls-go1-20 v0.3.1
+	github.com/quic-go/qtls-go1-20 v0.3.0
 	golang.org/x/crypto v0.4.0
 	golang.org/x/exp v0.0.0-20221205204356-47842c84f3db
 	golang.org/x/net v0.10.0
diff --git a/go.sum b/go.sum
index c0ed9606..4039bb07 100644
--- a/go.sum
+++ b/go.sum
@@ -90,8 +90,8 @@ github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7q
 github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 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/qtls-go1-20 v0.3.1 h1:O4BLOM3hwfVF3AcktIylQXyl7Yi2iBNVy5QsV+ySxbg=
-github.com/quic-go/qtls-go1-20 v0.3.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
+github.com/quic-go/qtls-go1-20 v0.3.0 h1:NrCXmDl8BddZwO67vlvEpBTwT89bJfKYygxv4HQvuDk=
+github.com/quic-go/qtls-go1-20 v0.3.0/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
diff --git a/http3/client.go b/http3/client.go
index cc2400e2..d53391f0 100644
--- a/http3/client.go
+++ b/http3/client.go
@@ -429,8 +429,8 @@ func (c *client) doRequest(req *http.Request, conn quic.EarlyConnection, str qui
 	// Check that the server doesn't send more data in DATA frames than indicated by the Content-Length header (if set).
 	// See section 4.1.2 of RFC 9114.
 	var httpStr Stream
-	if _, ok := res.Header["Content-Length"]; ok && res.ContentLength >= 0 {
-		httpStr = newLengthLimitedStream(hstr, res.ContentLength)
+	if _, ok := req.Header["Content-Length"]; ok && req.ContentLength >= 0 {
+		httpStr = newLengthLimitedStream(hstr, req.ContentLength)
 	} else {
 		httpStr = hstr
 	}
diff --git a/internal/handshake/conn.go b/internal/handshake/conn.go
deleted file mode 100644
index 54af823b..00000000
--- a/internal/handshake/conn.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package handshake
-
-import (
-	"net"
-	"time"
-)
-
-type conn struct {
-	localAddr, remoteAddr net.Addr
-}
-
-var _ net.Conn = &conn{}
-
-func (c *conn) Read([]byte) (int, error)         { return 0, nil }
-func (c *conn) Write([]byte) (int, error)        { return 0, nil }
-func (c *conn) Close() error                     { return nil }
-func (c *conn) RemoteAddr() net.Addr             { return c.remoteAddr }
-func (c *conn) LocalAddr() net.Addr              { return c.localAddr }
-func (c *conn) SetReadDeadline(time.Time) error  { return nil }
-func (c *conn) SetWriteDeadline(time.Time) error { return nil }
-func (c *conn) SetDeadline(time.Time) error      { return nil }
diff --git a/internal/handshake/crypto_setup.go b/internal/handshake/crypto_setup.go
index 543e70e0..66ed04d6 100644
--- a/internal/handshake/crypto_setup.go
+++ b/internal/handshake/crypto_setup.go
@@ -6,7 +6,6 @@ import (
 	"crypto/tls"
 	"errors"
 	"fmt"
-	"net"
 	"sync"
 	"sync/atomic"
 	"time"
@@ -105,7 +104,6 @@ func NewCryptoSetupClient(
 // NewCryptoSetupServer creates a new crypto setup for the server
 func NewCryptoSetupServer(
 	connID protocol.ConnectionID,
-	localAddr, remoteAddr net.Addr,
 	tp *wire.TransportParameters,
 	tlsConf *tls.Config,
 	allow0RTT bool,
@@ -127,7 +125,6 @@ func NewCryptoSetupServer(
 
 	quicConf := &qtls.QUICConfig{TLSConfig: tlsConf}
 	qtls.SetupConfigForServer(quicConf, cs.allow0RTT, cs.getDataForSessionTicket, cs.accept0RTT)
-	addConnToClientHelloInfo(quicConf.TLSConfig, localAddr, remoteAddr)
 
 	cs.tlsConf = quicConf.TLSConfig
 	cs.conn = qtls.QUICServer(quicConf)
@@ -135,31 +132,6 @@ func NewCryptoSetupServer(
 	return cs
 }
 
-// The tls.Config contains two callbacks that pass in a tls.ClientHelloInfo.
-// Since crypto/tls doesn't do it, we need to make sure to set the Conn field with a fake net.Conn
-// that allows the caller to get the local and the remote address.
-func addConnToClientHelloInfo(conf *tls.Config, localAddr, remoteAddr net.Addr) {
-	if conf.GetConfigForClient != nil {
-		gcfc := conf.GetConfigForClient
-		conf.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
-			info.Conn = &conn{localAddr: localAddr, remoteAddr: remoteAddr}
-			c, err := gcfc(info)
-			if c != nil {
-				// We're returning a tls.Config here, so we need to apply this recursively.
-				addConnToClientHelloInfo(c, localAddr, remoteAddr)
-			}
-			return c, err
-		}
-	}
-	if conf.GetCertificate != nil {
-		gc := conf.GetCertificate
-		conf.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
-			info.Conn = &conn{localAddr: localAddr, remoteAddr: remoteAddr}
-			return gc(info)
-		}
-	}
-}
-
 func newCryptoSetup(
 	connID protocol.ConnectionID,
 	tp *wire.TransportParameters,
diff --git a/internal/wire/transport_parameters.go b/internal/wire/transport_parameters.go
index dc0aa22f..1ec1e59d 100644
--- a/internal/wire/transport_parameters.go
+++ b/internal/wire/transport_parameters.go
@@ -481,18 +481,6 @@ func (p *TransportParameters) ValidFor0RTT(saved *TransportParameters) bool {
 		p.ActiveConnectionIDLimit == saved.ActiveConnectionIDLimit
 }
 
-// ValidForUpdate checks that the new transport parameters don't reduce limits after resuming a 0-RTT connection.
-// It is only used on the client side.
-func (p *TransportParameters) ValidForUpdate(saved *TransportParameters) bool {
-	return p.ActiveConnectionIDLimit >= saved.ActiveConnectionIDLimit &&
-		p.InitialMaxData >= saved.InitialMaxData &&
-		p.InitialMaxStreamDataBidiLocal >= saved.InitialMaxStreamDataBidiLocal &&
-		p.InitialMaxStreamDataBidiRemote >= saved.InitialMaxStreamDataBidiRemote &&
-		p.InitialMaxStreamDataUni >= saved.InitialMaxStreamDataUni &&
-		p.MaxBidiStreamNum >= saved.MaxBidiStreamNum &&
-		p.MaxUniStreamNum >= saved.MaxUniStreamNum
-}
-
 // String returns a string representation, intended for logging.
 func (p *TransportParameters) String() string {
 	logString := "&wire.TransportParameters{OriginalDestinationConnectionID: %s, InitialSourceConnectionID: %s, "
diff --git a/internal/wire/version_negotiation.go b/internal/wire/version_negotiation.go
index 3dc62113..afde70fa 100644
--- a/internal/wire/version_negotiation.go
+++ b/internal/wire/version_negotiation.go
@@ -40,7 +40,10 @@ func ComposeVersionNegotiation(destConnID, srcConnID protocol.ArbitraryLenConnec
 	buf := bytes.NewBuffer(make([]byte, 0, expectedLen))
 	r := make([]byte, 1)
 	_, _ = rand.Read(r) // ignore the error here. It is not critical to have perfect random here.
-	buf.WriteByte(r[0] | 0x80)
+	// Setting the "QUIC bit" (0x40) is not required by the RFC,
+	// but it allows clients to demultiplex QUIC with a long list of other protocols.
+	// See RFC 9443 and https://mailarchive.ietf.org/arch/msg/quic/oR4kxGKY6mjtPC1CZegY1ED4beg/ for details.
+	buf.WriteByte(r[0] | 0xc0)
 	utils.BigEndian.WriteUint32(buf, 0) // version 0
 	buf.WriteByte(uint8(destConnID.Len()))
 	buf.Write(destConnID.Bytes())


Test patch:
diff --git a/connection_test.go b/connection_test.go
index 5c972733..20e9872b 100644
--- a/connection_test.go
+++ b/connection_test.go
@@ -1891,6 +1891,7 @@ var _ = Describe("Connection", func() {
 
 	It("cancels the HandshakeComplete context when the handshake completes", func() {
 		packer.EXPECT().PackCoalescedPacket(false, gomock.Any(), conn.version).AnyTimes()
+		finishHandshake := make(chan struct{})
 		sph := mockackhandler.NewMockSentPacketHandler(mockCtrl)
 		conn.sentPacketHandler = sph
 		tracer.EXPECT().DroppedEncryptionLevel(protocol.EncryptionHandshake)
@@ -1900,26 +1901,53 @@ var _ = Describe("Connection", func() {
 		sph.EXPECT().DropPackets(protocol.EncryptionHandshake)
 		sph.EXPECT().SetHandshakeConfirmed()
 		connRunner.EXPECT().Retire(clientDestConnID)
-		cryptoSetup.EXPECT().SetHandshakeConfirmed()
-		cryptoSetup.EXPECT().GetSessionTicket()
+		go func() {
+			defer GinkgoRecover()
+			<-finishHandshake
+			cryptoSetup.EXPECT().StartHandshake()
+			cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventHandshakeComplete})
+			cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
+			cryptoSetup.EXPECT().SetHandshakeConfirmed()
+			cryptoSetup.EXPECT().GetSessionTicket()
+			conn.run()
+		}()
 		handshakeCtx := conn.HandshakeComplete()
 		Consistently(handshakeCtx).ShouldNot(BeClosed())
-		Expect(conn.handleHandshakeComplete()).To(Succeed())
+		close(finishHandshake)
 		Eventually(handshakeCtx).Should(BeClosed())
+		// make sure the go routine returns
+		streamManager.EXPECT().CloseWithError(gomock.Any())
+		expectReplaceWithClosed()
+		packer.EXPECT().PackApplicationClose(gomock.Any(), gomock.Any(), conn.version).Return(&coalescedPacket{buffer: getPacketBuffer()}, nil)
+		cryptoSetup.EXPECT().Close()
+		mconn.EXPECT().Write(gomock.Any(), gomock.Any())
+		tracer.EXPECT().ClosedConnection(gomock.Any())
+		tracer.EXPECT().Close()
+		conn.shutdown()
+		Eventually(conn.Context().Done()).Should(BeClosed())
 	})
 
 	It("sends a session ticket when the handshake completes", func() {
 		const size = protocol.MaxPostHandshakeCryptoFrameSize * 3 / 2
 		packer.EXPECT().PackCoalescedPacket(false, gomock.Any(), conn.version).AnyTimes()
+		finishHandshake := make(chan struct{})
 		connRunner.EXPECT().Retire(clientDestConnID)
 		conn.sentPacketHandler.DropPackets(protocol.EncryptionInitial)
 		tracer.EXPECT().DroppedEncryptionLevel(protocol.EncryptionHandshake)
-		cryptoSetup.EXPECT().SetHandshakeConfirmed()
-		cryptoSetup.EXPECT().GetSessionTicket().Return(make([]byte, size), nil)
+		go func() {
+			defer GinkgoRecover()
+			<-finishHandshake
+			cryptoSetup.EXPECT().StartHandshake()
+			cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventHandshakeComplete})
+			cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
+			cryptoSetup.EXPECT().SetHandshakeConfirmed()
+			cryptoSetup.EXPECT().GetSessionTicket().Return(make([]byte, size), nil)
+			conn.run()
+		}()
 
 		handshakeCtx := conn.HandshakeComplete()
 		Consistently(handshakeCtx).ShouldNot(BeClosed())
-		Expect(conn.handleHandshakeComplete()).To(Succeed())
+		close(finishHandshake)
 		var frames []ackhandler.Frame
 		Eventually(func() []ackhandler.Frame {
 			frames, _ = conn.framer.AppendControlFrames(nil, protocol.MaxByteCount, protocol.Version1)
@@ -1935,6 +1963,16 @@ var _ = Describe("Connection", func() {
 			}
 		}
 		Expect(size).To(BeEquivalentTo(s))
+		// make sure the go routine returns
+		streamManager.EXPECT().CloseWithError(gomock.Any())
+		expectReplaceWithClosed()
+		packer.EXPECT().PackApplicationClose(gomock.Any(), gomock.Any(), conn.version).Return(&coalescedPacket{buffer: getPacketBuffer()}, nil)
+		cryptoSetup.EXPECT().Close()
+		mconn.EXPECT().Write(gomock.Any(), gomock.Any())
+		tracer.EXPECT().ClosedConnection(gomock.Any())
+		tracer.EXPECT().Close()
+		conn.shutdown()
+		Eventually(conn.Context().Done()).Should(BeClosed())
 	})
 
 	It("doesn't cancel the HandshakeComplete context when the handshake fails", func() {
@@ -1989,7 +2027,6 @@ var _ = Describe("Connection", func() {
 			cryptoSetup.EXPECT().SetHandshakeConfirmed()
 			cryptoSetup.EXPECT().GetSessionTicket()
 			mconn.EXPECT().Write(gomock.Any(), gomock.Any())
-			Expect(conn.handleHandshakeComplete()).To(Succeed())
 			conn.run()
 		}()
 		Eventually(done).Should(BeClosed())
@@ -2313,7 +2350,6 @@ var _ = Describe("Connection", func() {
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
 				cryptoSetup.EXPECT().GetSessionTicket().MaxTimes(1)
 				cryptoSetup.EXPECT().SetHandshakeConfirmed().MaxTimes(1)
-				Expect(conn.handleHandshakeComplete()).To(Succeed())
 				err := conn.run()
 				nerr, ok := err.(net.Error)
 				Expect(ok).To(BeTrue())
@@ -2831,10 +2867,7 @@ var _ = Describe("Client Connection", func() {
 					TransportParameters: params,
 				})
 				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventHandshakeComplete}).MaxTimes(1)
-				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent}).MaxTimes(1).Do(func() {
-					defer GinkgoRecover()
-					Expect(conn.handleHandshakeComplete()).To(Succeed())
-				})
+				cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent}).MaxTimes(1)
 				errChan <- conn.run()
 				close(errChan)
 			}()
@@ -3002,40 +3035,6 @@ var _ = Describe("Client Connection", func() {
 				ErrorMessage: "expected original_destination_connection_id to equal deadbeef, is decafbad",
 			})))
 		})
-
-		It("errors if the transport parameters contain reduced limits after knowing 0-RTT data is accepted by the server", func() {
-			conn.perspective = protocol.PerspectiveClient
-			conn.peerParams = &wire.TransportParameters{
-				ActiveConnectionIDLimit:        3,
-				InitialMaxData:                 0x5000,
-				InitialMaxStreamDataBidiLocal:  0x5000,
-				InitialMaxStreamDataBidiRemote: 1000,
-				InitialMaxStreamDataUni:        1000,
-				MaxBidiStreamNum:               500,
-				MaxUniStreamNum:                500,
-			}
-			params := &wire.TransportParameters{
-				OriginalDestinationConnectionID: destConnID,
-				InitialSourceConnectionID:       destConnID,
-				ActiveConnectionIDLimit:         3,
-				InitialMaxData:                  0x5000,
-				InitialMaxStreamDataBidiLocal:   0x5000,
-				InitialMaxStreamDataBidiRemote:  1000,
-				InitialMaxStreamDataUni:         1000,
-				MaxBidiStreamNum:                300,
-				MaxUniStreamNum:                 300,
-			}
-			expectClose(false, true)
-			processed := make(chan struct{})
-			tracer.EXPECT().ReceivedTransportParameters(params).Do(func(*wire.TransportParameters) { close(processed) })
-			cryptoSetup.EXPECT().ConnectionState().Return(handshake.ConnectionState{Used0RTT: true})
-			paramsChan <- params
-			Eventually(processed).Should(BeClosed())
-			Eventually(errChan).Should(Receive(MatchError(&qerr.TransportError{
-				ErrorCode:    qerr.ProtocolViolation,
-				ErrorMessage: "server sent reduced limits after accepting 0-RTT data",
-			})))
-		})
 	})
 
 	Context("handling potentially injected packets", func() {
diff --git a/integrationtests/gomodvendor/go.sum b/integrationtests/gomodvendor/go.sum
index ad9adb31..1336cd7c 100644
--- a/integrationtests/gomodvendor/go.sum
+++ b/integrationtests/gomodvendor/go.sum
@@ -136,8 +136,8 @@ github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7q
 github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 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/qtls-go1-20 v0.3.1 h1:O4BLOM3hwfVF3AcktIylQXyl7Yi2iBNVy5QsV+ySxbg=
-github.com/quic-go/qtls-go1-20 v0.3.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
+github.com/quic-go/qtls-go1-20 v0.3.0 h1:NrCXmDl8BddZwO67vlvEpBTwT89bJfKYygxv4HQvuDk=
+github.com/quic-go/qtls-go1-20 v0.3.0/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
diff --git a/integrationtests/self/handshake_test.go b/integrationtests/self/handshake_test.go
index cccff8a6..2ef4dd20 100644
--- a/integrationtests/self/handshake_test.go
+++ b/integrationtests/self/handshake_test.go
@@ -140,39 +140,6 @@ var _ = Describe("Handshake tests", func() {
 			Expect(err).ToNot(HaveOccurred())
 		})
 
-		It("has the right local and remote address on the tls.Config.GetConfigForClient ClientHelloInfo.Conn", func() {
-			var local, remote net.Addr
-			var local2, remote2 net.Addr
-			done := make(chan struct{})
-			tlsConf := &tls.Config{
-				GetConfigForClient: func(info *tls.ClientHelloInfo) (*tls.Config, error) {
-					local = info.Conn.LocalAddr()
-					remote = info.Conn.RemoteAddr()
-					conf := getTLSConfig()
-					conf.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
-						defer close(done)
-						local2 = info.Conn.LocalAddr()
-						remote2 = info.Conn.RemoteAddr()
-						return &(conf.Certificates[0]), nil
-					}
-					return conf, nil
-				},
-			}
-			runServer(tlsConf)
-			conn, err := quic.DialAddr(
-				context.Background(),
-				fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
-				getTLSClientConfig(),
-				getQuicConfig(nil),
-			)
-			Expect(err).ToNot(HaveOccurred())
-			Eventually(done).Should(BeClosed())
-			Expect(server.Addr()).To(Equal(local))
-			Expect(conn.LocalAddr().(*net.UDPAddr).Port).To(Equal(remote.(*net.UDPAddr).Port))
-			Expect(local).To(Equal(local2))
-			Expect(remote).To(Equal(remote2))
-		})
-
 		It("works with a long certificate chain", func() {
 			runServer(getTLSConfigWithLongCertChain())
 			_, err := quic.DialAddr(
diff --git a/internal/handshake/crypto_setup_test.go b/internal/handshake/crypto_setup_test.go
index 8b2c5efe..e891cb9f 100644
--- a/internal/handshake/crypto_setup_test.go
+++ b/internal/handshake/crypto_setup_test.go
@@ -7,7 +7,6 @@ import (
 	"crypto/x509"
 	"crypto/x509/pkix"
 	"math/big"
-	"net"
 	"time"
 
 	mocktls "github.com/quic-go/quic-go/internal/mocks/tls"
@@ -29,25 +28,6 @@ const (
 )
 
 var _ = Describe("Crypto Setup TLS", func() {
-	generateCert := func() tls.Certificate {
-		priv, err := rsa.GenerateKey(rand.Reader, 2048)
-		Expect(err).ToNot(HaveOccurred())
-		tmpl := &x509.Certificate{
-			SerialNumber:          big.NewInt(1),
-			Subject:               pkix.Name{},
-			SignatureAlgorithm:    x509.SHA256WithRSA,
-			NotBefore:             time.Now(),
-			NotAfter:              time.Now().Add(time.Hour), // valid for an hour
-			BasicConstraintsValid: true,
-		}
-		certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, priv.Public(), priv)
-		Expect(err).ToNot(HaveOccurred())
-		return tls.Certificate{
-			PrivateKey:  priv,
-			Certificate: [][]byte{certDER},
-		}
-	}
-
 	var clientConf, serverConf *tls.Config
 
 	BeforeEach(func() {
@@ -85,8 +65,6 @@ var _ = Describe("Crypto Setup TLS", func() {
 		var token protocol.StatelessResetToken
 		server := NewCryptoSetupServer(
 			protocol.ConnectionID{},
-			&net.UDPAddr{IP: net.IPv6loopback, Port: 1234},
-			&net.UDPAddr{IP: net.IPv6loopback, Port: 4321},
 			&wire.TransportParameters{StatelessResetToken: &token},
 			testdata.GetTLSConfig(),
 			false,
@@ -105,69 +83,26 @@ var _ = Describe("Crypto Setup TLS", func() {
 		Expect(err.Error()).To(ContainSubstring("tls: handshake data received at wrong level"))
 	})
 
-	Context("filling in a net.Conn in tls.ClientHelloInfo", func() {
-		var (
-			local  = &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 42}
-			remote = &net.UDPAddr{IP: net.IPv4(192, 168, 0, 1), Port: 1337}
-		)
-
-		It("wraps GetCertificate", func() {
-			var localAddr, remoteAddr net.Addr
-			tlsConf := &tls.Config{
-				GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
-					localAddr = info.Conn.LocalAddr()
-					remoteAddr = info.Conn.RemoteAddr()
-					cert := generateCert()
-					return &cert, nil
-				},
-			}
-			addConnToClientHelloInfo(tlsConf, local, remote)
-			_, err := tlsConf.GetCertificate(&tls.ClientHelloInfo{})
+	Context("doing the handshake", func() {
+		generateCert := func() tls.Certificate {
+			priv, err := rsa.GenerateKey(rand.Reader, 2048)
 			Expect(err).ToNot(HaveOccurred())
-			Expect(localAddr).To(Equal(local))
-			Expect(remoteAddr).To(Equal(remote))
-		})
-
-		It("wraps GetConfigForClient", func() {
-			var localAddr, remoteAddr net.Addr
-			tlsConf := &tls.Config{
-				GetConfigForClient: func(info *tls.ClientHelloInfo) (*tls.Config, error) {
-					localAddr = info.Conn.LocalAddr()
-					remoteAddr = info.Conn.RemoteAddr()
-					return &tls.Config{}, nil
-				},
+			tmpl := &x509.Certificate{
+				SerialNumber:          big.NewInt(1),
+				Subject:               pkix.Name{},
+				SignatureAlgorithm:    x509.SHA256WithRSA,
+				NotBefore:             time.Now(),
+				NotAfter:              time.Now().Add(time.Hour), // valid for an hour
+				BasicConstraintsValid: true,
 			}
-			addConnToClientHelloInfo(tlsConf, local, remote)
-			_, err := tlsConf.GetConfigForClient(&tls.ClientHelloInfo{})
+			certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, priv.Public(), priv)
 			Expect(err).ToNot(HaveOccurred())
-			Expect(localAddr).To(Equal(local))
-			Expect(remoteAddr).To(Equal(remote))
-		})
-
-		It("wraps GetConfigForClient, recursively", func() {
-			var localAddr, remoteAddr net.Addr
-			tlsConf := &tls.Config{}
-			tlsConf.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
-				conf := tlsConf.Clone()
-				conf.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
-					localAddr = info.Conn.LocalAddr()
-					remoteAddr = info.Conn.RemoteAddr()
-					cert := generateCert()
-					return &cert, nil
-				}
-				return conf, nil
+			return tls.Certificate{
+				PrivateKey:  priv,
+				Certificate: [][]byte{certDER},
 			}
-			addConnToClientHelloInfo(tlsConf, local, remote)
-			conf, err := tlsConf.GetConfigForClient(&tls.ClientHelloInfo{})
-			Expect(err).ToNot(HaveOccurred())
-			_, err = conf.GetCertificate(&tls.ClientHelloInfo{})
-			Expect(err).ToNot(HaveOccurred())
-			Expect(localAddr).To(Equal(local))
-			Expect(remoteAddr).To(Equal(remote))
-		})
-	})
+		}
 
-	Context("doing the handshake", func() {
 		newRTTStatsWithRTT := func(rtt time.Duration) *utils.RTTStats {
 			rttStats := &utils.RTTStats{}
 			rttStats.UpdateRTT(rtt, 0, time.Now())
@@ -269,8 +204,6 @@ var _ = Describe("Crypto Setup TLS", func() {
 			}
 			server := NewCryptoSetupServer(
 				protocol.ConnectionID{},
-				&net.UDPAddr{IP: net.IPv6loopback, Port: 1234},
-				&net.UDPAddr{IP: net.IPv6loopback, Port: 4321},
 				serverTransportParameters,
 				serverConf,
 				enable0RTT,
@@ -340,8 +273,6 @@ var _ = Describe("Crypto Setup TLS", func() {
 			}
 			server := NewCryptoSetupServer(
 				protocol.ConnectionID{},
-				&net.UDPAddr{IP: net.IPv6loopback, Port: 1234},
-				&net.UDPAddr{IP: net.IPv6loopback, Port: 4321},
 				sTransportParameters,
 				serverConf,
 				false,
diff --git a/internal/wire/transport_parameter_test.go b/internal/wire/transport_parameter_test.go
index 9dd306b7..95bfbafc 100644
--- a/internal/wire/transport_parameter_test.go
+++ b/internal/wire/transport_parameter_test.go
@@ -612,93 +612,5 @@ var _ = Describe("Transport Parameters", func() {
 				Expect(p.ValidFor0RTT(saved)).To(BeFalse())
 			})
 		})
-
-		Context("client checks the parameters after successfully sending 0-RTT data", func() {
-			var p TransportParameters
-			saved := &TransportParameters{
-				InitialMaxStreamDataBidiLocal:  1,
-				InitialMaxStreamDataBidiRemote: 2,
-				InitialMaxStreamDataUni:        3,
-				InitialMaxData:                 4,
-				MaxBidiStreamNum:               5,
-				MaxUniStreamNum:                6,
-				ActiveConnectionIDLimit:        7,
-			}
-
-			BeforeEach(func() {
-				p = *saved
-				Expect(p.ValidForUpdate(saved)).To(BeTrue())
-			})
-
-			It("rejects the parameters if the InitialMaxStreamDataBidiLocal was reduced", func() {
-				p.InitialMaxStreamDataBidiLocal = saved.InitialMaxStreamDataBidiLocal - 1
-				Expect(p.ValidForUpdate(saved)).To(BeFalse())
-			})
-
-			It("doesn't reject the parameters if the InitialMaxStreamDataBidiLocal was increased", func() {
-				p.InitialMaxStreamDataBidiLocal = saved.InitialMaxStreamDataBidiLocal + 1
-				Expect(p.ValidForUpdate(saved)).To(BeTrue())
-			})
-
-			It("rejects the parameters if the InitialMaxStreamDataBidiRemote was reduced", func() {
-				p.InitialMaxStreamDataBidiRemote = saved.InitialMaxStreamDataBidiRemote - 1
-				Expect(p.ValidForUpdate(saved)).To(BeFalse())
-			})
-
-			It("doesn't reject the parameters if the InitialMaxStreamDataBidiRemote was increased", func() {
-				p.InitialMaxStreamDataBidiRemote = saved.InitialMaxStreamDataBidiRemote + 1
-				Expect(p.ValidForUpdate(saved)).To(BeTrue())
-			})
-
-			It("rejects the parameters if the InitialMaxStreamDataUni was reduced", func() {
-				p.InitialMaxStreamDataUni = saved.InitialMaxStreamDataUni - 1
-				Expect(p.ValidForUpdate(saved)).To(BeFalse())
-			})
-
-			It("doesn't reject the parameters if the InitialMaxStreamDataUni was increased", func() {
-				p.InitialMaxStreamDataUni = saved.InitialMaxStreamDataUni + 1
-				Expect(p.ValidForUpdate(saved)).To(BeTrue())
-			})
-
-			It("rejects the parameters if the InitialMaxData was reduced", func() {
-				p.InitialMaxData = saved.InitialMaxData - 1
-				Expect(p.ValidForUpdate(saved)).To(BeFalse())
-			})
-
-			It("doesn't reject the parameters if the InitialMaxData was increased", func() {
-				p.InitialMaxData = saved.InitialMaxData + 1
-				Expect(p.ValidForUpdate(saved)).To(BeTrue())
-			})
-
-			It("rejects the parameters if the MaxBidiStreamNum was reduced", func() {
-				p.MaxBidiStreamNum = saved.MaxBidiStreamNum - 1
-				Expect(p.ValidForUpdate(saved)).To(BeFalse())
-			})
-
-			It("doesn't reject the parameters if the MaxBidiStreamNum was increased", func() {
-				p.MaxBidiStreamNum = saved.MaxBidiStreamNum + 1
-				Expect(p.ValidForUpdate(saved)).To(BeTrue())
-			})
-
-			It("rejects the parameters if the MaxUniStreamNum reduced", func() {
-				p.MaxUniStreamNum = saved.MaxUniStreamNum - 1
-				Expect(p.ValidForUpdate(saved)).To(BeFalse())
-			})
-
-			It("doesn't reject the parameters if the MaxUniStreamNum was increased", func() {
-				p.MaxUniStreamNum = saved.MaxUniStreamNum + 1
-				Expect(p.ValidForUpdate(saved)).To(BeTrue())
-			})
-
-			It("rejects the parameters if the ActiveConnectionIDLimit reduced", func() {
-				p.ActiveConnectionIDLimit = saved.ActiveConnectionIDLimit - 1
-				Expect(p.ValidForUpdate(saved)).To(BeFalse())
-			})
-
-			It("doesn't reject the parameters if the ActiveConnectionIDLimit increased", func() {
-				p.ActiveConnectionIDLimit = saved.ActiveConnectionIDLimit + 1
-				Expect(p.ValidForUpdate(saved)).To(BeTrue())
-			})
-		})
 	})
 })
diff --git a/internal/wire/version_negotiation_test.go b/internal/wire/version_negotiation_test.go
index 65d0ba91..acd6d7ce 100644
--- a/internal/wire/version_negotiation_test.go
+++ b/internal/wire/version_negotiation_test.go
@@ -64,6 +64,7 @@ var _ = Describe("Version Negotiation Packets", func() {
 		versions := []protocol.VersionNumber{1001, 1003}
 		data := ComposeVersionNegotiation(destConnID, srcConnID, versions)
 		Expect(IsLongHeaderPacket(data[0])).To(BeTrue())
+		Expect(data[0] & 0x40).ToNot(BeZero())
 		v, err := ParseVersion(data)
 		Expect(err).ToNot(HaveOccurred())
 		Expect(v).To(BeZero())