Repository: quic-go/quic-go
Pull request #4120 (2023-10-23 05:31:48)

use typed atomics in integration tests


Patch:
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index c661e09c..6f9b27fe 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -13,6 +13,9 @@ jobs:
           - os: "macos"
             go: "1.21.x"
     runs-on: ${{ fromJSON(vars[format('INTEGRATION_RUNNER_{0}', matrix.os)] || format('"{0}-latest"', matrix.os)) }}
+    defaults:
+      run:
+        shell: bash # by default Windows uses PowerShell, which uses a different syntax for setting environment variables
     env:
       DEBUG: false # set this to true to export qlogs and save them as artifacts
       TIMESCALE_FACTOR: 3
@@ -56,7 +59,8 @@ jobs:
           go run github.com/onsi/ginkgo/v2/ginkgo -r -v -randomize-all -randomize-suites -trace integrationtests/self -- ${{ env.QLOGFLAG }}
       - name: save qlogs
         if: ${{ always() && env.DEBUG == 'true' }}
-        uses: actions/upload-artifact@v2
+        uses: actions/upload-artifact@v3
         with:
-          name: qlogs
+          name: qlogs-${{ matrix.os }}-go${{ matrix.go }}
           path: integrationtests/self/*.qlog
+          retention-days: 7
diff --git a/README.md b/README.md
index e6beb8aa..30265c3a 100644
--- a/README.md
+++ b/README.md
@@ -159,18 +159,18 @@ On the receiver side, this is surfaced as a `quic.ApplicationError`.
 
 Unreliable datagrams are a QUIC extension ([RFC 9221](https://datatracker.ietf.org/doc/html/rfc9221)) that is negotiated during the handshake. Support can be enabled by setting the `quic.Config.EnableDatagram` flag. Note that this doesn't guarantee that the peer also supports datagrams. Whether or not the feature negotiation succeeded can be learned from the `quic.ConnectionState.SupportsDatagrams` obtained from `quic.Connection.ConnectionState()`.
 
-QUIC DATAGRAMs are a new QUIC frame type sent in QUIC 1-RTT packets (i.e. after completion of the handshake). Therefore, they're end-to-end encrypted and congestion-controlled. However, if a DATAGRAM frame is deemed lost by QUIC's loss detection mechanism, they are not automatically retransmitted.
+QUIC DATAGRAMs are a new QUIC frame type sent in QUIC 1-RTT packets (i.e. after completion of the handshake). Therefore, they're end-to-end encrypted and congestion-controlled. However, if a DATAGRAM frame is deemed lost by QUIC's loss detection mechanism, they are not retransmitted.
 
-Datagrams are sent using the `SendMessage` method on the `quic.Connection`:
+Datagrams are sent using the `SendDatagram` method on the `quic.Connection`:
 
 ```go
-conn.SendMessage([]byte("foobar"))
+conn.SendDatagram([]byte("foobar"))
 ```
 
-And received using `ReceiveMessage`:
+And received using `ReceiveDatagram`:
 
 ```go
-msg, err := conn.ReceiveMessage()
+msg, err := conn.ReceiveDatagram()
 ```
 
 Note that this code path is currently not optimized. It works for datagrams that are sent occasionally, but it doesn't achieve the same throughput as writing data on a stream. Please get in touch on issue #3766 if your use case relies on high datagram throughput, or if you'd like to help fix this issue. There are also some restrictions regarding the maximum message size (see #3599).
diff --git a/connection.go b/connection.go
index 09b522a9..93c15913 100644
--- a/connection.go
+++ b/connection.go
@@ -2343,7 +2343,7 @@ func (s *connection) onStreamCompleted(id protocol.StreamID) {
 	}
 }
 
-func (s *connection) SendMessage(p []byte) error {
+func (s *connection) SendDatagram(p []byte) error {
 	if !s.supportsDatagrams() {
 		return errors.New("datagram support disabled")
 	}
@@ -2357,7 +2357,7 @@ func (s *connection) SendMessage(p []byte) error {
 	return s.datagramQueue.AddAndWait(f)
 }
 
-func (s *connection) ReceiveMessage(ctx context.Context) ([]byte, error) {
+func (s *connection) ReceiveDatagram(ctx context.Context) ([]byte, error) {
 	if !s.config.EnableDatagrams {
 		return nil, errors.New("datagram support disabled")
 	}
diff --git a/interface.go b/interface.go
index 6eac385d..da0e5e2b 100644
--- a/interface.go
+++ b/interface.go
@@ -187,10 +187,10 @@ type Connection interface {
 	// Warning: This API should not be considered stable and might change soon.
 	ConnectionState() ConnectionState
 
-	// SendMessage sends a message as a datagram, as specified in RFC 9221.
-	SendMessage([]byte) error
-	// ReceiveMessage gets a message received in a datagram, as specified in RFC 9221.
-	ReceiveMessage(context.Context) ([]byte, error)
+	// SendDatagram sends a message as a datagram, as specified in RFC 9221.
+	SendDatagram([]byte) error
+	// ReceiveDatagram gets a message received in a datagram, as specified in RFC 9221.
+	ReceiveDatagram(context.Context) ([]byte, error)
 }
 
 // An EarlyConnection is a connection that is handshaking.
@@ -338,7 +338,7 @@ type ConnectionState struct {
 	// SupportsDatagrams says if support for QUIC datagrams (RFC 9221) was negotiated.
 	// This requires both nodes to support and enable the datagram extensions (via Config.EnableDatagrams).
 	// If datagram support was negotiated, datagrams can be sent and received using the
-	// SendMessage and ReceiveMessage methods on the Connection.
+	// SendDatagram and ReceiveDatagram methods on the Connection.
 	SupportsDatagrams bool
 	// Used0RTT says if 0-RTT resumption was used.
 	Used0RTT bool
diff --git a/internal/mocks/quic/early_conn.go b/internal/mocks/quic/early_conn.go
index 223def6c..2e726a82 100644
--- a/internal/mocks/quic/early_conn.go
+++ b/internal/mocks/quic/early_conn.go
@@ -503,41 +503,41 @@ func (c *EarlyConnectionOpenUniStreamSyncCall) DoAndReturn(f func(context.Contex
 	return c
 }
 
-// ReceiveMessage mocks base method.
-func (m *MockEarlyConnection) ReceiveMessage(arg0 context.Context) ([]byte, error) {
+// ReceiveDatagram mocks base method.
+func (m *MockEarlyConnection) ReceiveDatagram(arg0 context.Context) ([]byte, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "ReceiveMessage", arg0)
+	ret := m.ctrl.Call(m, "ReceiveDatagram", arg0)
 	ret0, _ := ret[0].([]byte)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
-// ReceiveMessage indicates an expected call of ReceiveMessage.
-func (mr *MockEarlyConnectionMockRecorder) ReceiveMessage(arg0 any) *EarlyConnectionReceiveMessageCall {
+// ReceiveDatagram indicates an expected call of ReceiveDatagram.
+func (mr *MockEarlyConnectionMockRecorder) ReceiveDatagram(arg0 any) *EarlyConnectionReceiveDatagramCall {
 	mr.mock.ctrl.T.Helper()
-	call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReceiveMessage", reflect.TypeOf((*MockEarlyConnection)(nil).ReceiveMessage), arg0)
-	return &EarlyConnectionReceiveMessageCall{Call: call}
+	call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReceiveDatagram", reflect.TypeOf((*MockEarlyConnection)(nil).ReceiveDatagram), arg0)
+	return &EarlyConnectionReceiveDatagramCall{Call: call}
 }
 
-// EarlyConnectionReceiveMessageCall wrap *gomock.Call
-type EarlyConnectionReceiveMessageCall struct {
+// EarlyConnectionReceiveDatagramCall wrap *gomock.Call
+type EarlyConnectionReceiveDatagramCall struct {
 	*gomock.Call
 }
 
 // Return rewrite *gomock.Call.Return
-func (c *EarlyConnectionReceiveMessageCall) Return(arg0 []byte, arg1 error) *EarlyConnectionReceiveMessageCall {
+func (c *EarlyConnectionReceiveDatagramCall) Return(arg0 []byte, arg1 error) *EarlyConnectionReceiveDatagramCall {
 	c.Call = c.Call.Return(arg0, arg1)
 	return c
 }
 
 // Do rewrite *gomock.Call.Do
-func (c *EarlyConnectionReceiveMessageCall) Do(f func(context.Context) ([]byte, error)) *EarlyConnectionReceiveMessageCall {
+func (c *EarlyConnectionReceiveDatagramCall) Do(f func(context.Context) ([]byte, error)) *EarlyConnectionReceiveDatagramCall {
 	c.Call = c.Call.Do(f)
 	return c
 }
 
 // DoAndReturn rewrite *gomock.Call.DoAndReturn
-func (c *EarlyConnectionReceiveMessageCall) DoAndReturn(f func(context.Context) ([]byte, error)) *EarlyConnectionReceiveMessageCall {
+func (c *EarlyConnectionReceiveDatagramCall) DoAndReturn(f func(context.Context) ([]byte, error)) *EarlyConnectionReceiveDatagramCall {
 	c.Call = c.Call.DoAndReturn(f)
 	return c
 }
@@ -580,40 +580,40 @@ func (c *EarlyConnectionRemoteAddrCall) DoAndReturn(f func() net.Addr) *EarlyCon
 	return c
 }
 
-// SendMessage mocks base method.
-func (m *MockEarlyConnection) SendMessage(arg0 []byte) error {
+// SendDatagram mocks base method.
+func (m *MockEarlyConnection) SendDatagram(arg0 []byte) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "SendMessage", arg0)
+	ret := m.ctrl.Call(m, "SendDatagram", arg0)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
-// SendMessage indicates an expected call of SendMessage.
-func (mr *MockEarlyConnectionMockRecorder) SendMessage(arg0 any) *EarlyConnectionSendMessageCall {
+// SendDatagram indicates an expected call of SendDatagram.
+func (mr *MockEarlyConnectionMockRecorder) SendDatagram(arg0 any) *EarlyConnectionSendDatagramCall {
 	mr.mock.ctrl.T.Helper()
-	call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockEarlyConnection)(nil).SendMessage), arg0)
-	return &EarlyConnectionSendMessageCall{Call: call}
+	call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendDatagram", reflect.TypeOf((*MockEarlyConnection)(nil).SendDatagram), arg0)
+	return &EarlyConnectionSendDatagramCall{Call: call}
 }
 
-// EarlyConnectionSendMessageCall wrap *gomock.Call
-type EarlyConnectionSendMessageCall struct {
+// EarlyConnectionSendDatagramCall wrap *gomock.Call
+type EarlyConnectionSendDatagramCall struct {
 	*gomock.Call
 }
 
 // Return rewrite *gomock.Call.Return
-func (c *EarlyConnectionSendMessageCall) Return(arg0 error) *EarlyConnectionSendMessageCall {
+func (c *EarlyConnectionSendDatagramCall) Return(arg0 error) *EarlyConnectionSendDatagramCall {
 	c.Call = c.Call.Return(arg0)
 	return c
 }
 
 // Do rewrite *gomock.Call.Do
-func (c *EarlyConnectionSendMessageCall) Do(f func([]byte) error) *EarlyConnectionSendMessageCall {
+func (c *EarlyConnectionSendDatagramCall) Do(f func([]byte) error) *EarlyConnectionSendDatagramCall {
 	c.Call = c.Call.Do(f)
 	return c
 }
 
 // DoAndReturn rewrite *gomock.Call.DoAndReturn
-func (c *EarlyConnectionSendMessageCall) DoAndReturn(f func([]byte) error) *EarlyConnectionSendMessageCall {
+func (c *EarlyConnectionSendDatagramCall) DoAndReturn(f func([]byte) error) *EarlyConnectionSendDatagramCall {
 	c.Call = c.Call.DoAndReturn(f)
 	return c
 }
diff --git a/sys_conn_helper_linux.go b/sys_conn_helper_linux.go
index 8e326af9..5fbf34ad 100644
--- a/sys_conn_helper_linux.go
+++ b/sys_conn_helper_linux.go
@@ -19,7 +19,7 @@ const (
 	ipv4PKTINFO  = unix.IP_PKTINFO
 )
 
-const ecnIPv4DataLen = 4
+const ecnIPv4DataLen = 1
 
 const batchSize = 8 // needs to smaller than MaxUint8 (otherwise the type of oobConn.readPos has to be changed)
 


Test patch:
diff --git a/integrationtests/self/cancelation_test.go b/integrationtests/self/cancelation_test.go
index 5f95c0b7..28f3e009 100644
--- a/integrationtests/self/cancelation_test.go
+++ b/integrationtests/self/cancelation_test.go
@@ -31,7 +31,7 @@ var _ = Describe("Stream Cancellations", func() {
 			server, err = quic.ListenAddr("localhost:0", getTLSConfig(), getQuicConfig(nil))
 			Expect(err).ToNot(HaveOccurred())
 
-			var canceledCounter int32
+			var canceledCounter atomic.Int32
 			go func() {
 				defer GinkgoRecover()
 				var wg sync.WaitGroup
@@ -50,18 +50,18 @@ var _ = Describe("Stream Cancellations", func() {
 								ErrorCode: quic.StreamErrorCode(str.StreamID()),
 								Remote:    true,
 							}))
-							atomic.AddInt32(&canceledCounter, 1)
+							canceledCounter.Add(1)
 							return
 						}
 						if err := str.Close(); err != nil {
 							Expect(err).To(MatchError(fmt.Sprintf("close called for canceled stream %d", str.StreamID())))
-							atomic.AddInt32(&canceledCounter, 1)
+							canceledCounter.Add(1)
 							return
 						}
 					}()
 				}
 				wg.Wait()
-				numCanceledStreamsChan <- atomic.LoadInt32(&canceledCounter)
+				numCanceledStreamsChan <- canceledCounter.Load()
 			}()
 			return numCanceledStreamsChan
 		}
@@ -80,7 +80,7 @@ var _ = Describe("Stream Cancellations", func() {
 			)
 			Expect(err).ToNot(HaveOccurred())
 
-			var canceledCounter int32
+			var canceledCounter atomic.Int32
 			var wg sync.WaitGroup
 			wg.Add(numStreams)
 			for i := 0; i < numStreams; i++ {
@@ -91,7 +91,7 @@ var _ = Describe("Stream Cancellations", func() {
 					Expect(err).ToNot(HaveOccurred())
 					// cancel around 2/3 of the streams
 					if rand.Int31()%3 != 0 {
-						atomic.AddInt32(&canceledCounter, 1)
+						canceledCounter.Add(1)
 						resetErr := quic.StreamErrorCode(str.StreamID())
 						str.CancelRead(resetErr)
 						_, err := str.Read([]byte{0})
@@ -113,7 +113,7 @@ var _ = Describe("Stream Cancellations", func() {
 			Eventually(serverCanceledCounterChan).Should(Receive(&serverCanceledCounter))
 			Expect(conn.CloseWithError(0, "")).To(Succeed())
 
-			clientCanceledCounter := atomic.LoadInt32(&canceledCounter)
+			clientCanceledCounter := canceledCounter.Load()
 			// The server will only count a stream as being reset if learns about the cancelation before it finished writing all data.
 			Expect(clientCanceledCounter).To(BeNumerically(">=", serverCanceledCounter))
 			fmt.Fprintf(GinkgoWriter, "Canceled reading on %d of %d streams.\n", clientCanceledCounter, numStreams)
@@ -132,7 +132,7 @@ var _ = Describe("Stream Cancellations", func() {
 			)
 			Expect(err).ToNot(HaveOccurred())
 
-			var canceledCounter int32
+			var canceledCounter atomic.Int32
 			var wg sync.WaitGroup
 			wg.Add(numStreams)
 			for i := 0; i < numStreams; i++ {
@@ -148,7 +148,7 @@ var _ = Describe("Stream Cancellations", func() {
 						Expect(err).ToNot(HaveOccurred())
 						str.CancelRead(quic.StreamErrorCode(str.StreamID()))
 						Expect(data).To(Equal(PRData[:length]))
-						atomic.AddInt32(&canceledCounter, 1)
+						canceledCounter.Add(1)
 						return
 					}
 					data, err := io.ReadAll(str)
@@ -162,7 +162,7 @@ var _ = Describe("Stream Cancellations", func() {
 			Eventually(serverCanceledCounterChan).Should(Receive(&serverCanceledCounter))
 			Expect(conn.CloseWithError(0, "")).To(Succeed())
 
-			clientCanceledCounter := atomic.LoadInt32(&canceledCounter)
+			clientCanceledCounter := canceledCounter.Load()
 			// The server will only count a stream as being reset if learns about the cancelation before it finished writing all data.
 			Expect(clientCanceledCounter).To(BeNumerically(">=", serverCanceledCounter))
 			fmt.Fprintf(GinkgoWriter, "Canceled reading on %d of %d streams.\n", clientCanceledCounter, numStreams)
@@ -185,7 +185,7 @@ var _ = Describe("Stream Cancellations", func() {
 
 			var wg sync.WaitGroup
 			wg.Add(numStreams)
-			var counter int32
+			var counter atomic.Int32
 			for i := 0; i < numStreams; i++ {
 				go func() {
 					defer GinkgoRecover()
@@ -199,7 +199,7 @@ var _ = Describe("Stream Cancellations", func() {
 						defer close(done)
 						b := make([]byte, 32)
 						if _, err := str.Read(b); err != nil {
-							atomic.AddInt32(&counter, 1)
+							counter.Add(1)
 							Expect(err).To(Equal(&quic.StreamError{
 								StreamID:  str.StreamID(),
 								ErrorCode: 1234,
@@ -214,7 +214,7 @@ var _ = Describe("Stream Cancellations", func() {
 			}
 			wg.Wait()
 			Expect(conn.CloseWithError(0, "")).To(Succeed())
-			numCanceled := atomic.LoadInt32(&counter)
+			numCanceled := counter.Load()
 			fmt.Fprintf(GinkgoWriter, "canceled %d out of %d streams", numCanceled, numStreams)
 			Expect(numCanceled).ToNot(BeZero())
 			Eventually(serverCanceledCounterChan).Should(Receive())
@@ -232,7 +232,7 @@ var _ = Describe("Stream Cancellations", func() {
 			Expect(err).ToNot(HaveOccurred())
 
 			var wg sync.WaitGroup
-			var counter int32
+			var counter atomic.Int32
 			wg.Add(numStreams)
 			for i := 0; i < numStreams; i++ {
 				go func() {
@@ -242,7 +242,7 @@ var _ = Describe("Stream Cancellations", func() {
 					Expect(err).ToNot(HaveOccurred())
 					data, err := io.ReadAll(str)
 					if err != nil {
-						atomic.AddInt32(&counter, 1)
+						counter.Add(1)
 						Expect(err).To(MatchError(&quic.StreamError{
 							StreamID:  str.StreamID(),
 							ErrorCode: quic.StreamErrorCode(str.StreamID()),
@@ -254,7 +254,7 @@ var _ = Describe("Stream Cancellations", func() {
 			}
 			wg.Wait()
 
-			streamCount := atomic.LoadInt32(&counter)
+			streamCount := counter.Load()
 			fmt.Fprintf(GinkgoWriter, "Canceled writing on %d of %d streams\n", streamCount, numStreams)
 			Expect(streamCount).To(BeNumerically(">", numStreams/10))
 			Expect(numStreams - streamCount).To(BeNumerically(">", numStreams/10))
@@ -267,7 +267,7 @@ var _ = Describe("Stream Cancellations", func() {
 			server, err := quic.ListenAddr("localhost:0", getTLSConfig(), nil)
 			Expect(err).ToNot(HaveOccurred())
 
-			var canceledCounter int32
+			var canceledCounter atomic.Int32
 			go func() {
 				defer GinkgoRecover()
 				conn, err := server.Accept(context.Background())
@@ -280,7 +280,7 @@ var _ = Describe("Stream Cancellations", func() {
 						// cancel about 2/3 of the streams
 						if rand.Int31()%3 != 0 {
 							str.CancelWrite(quic.StreamErrorCode(str.StreamID()))
-							atomic.AddInt32(&canceledCounter, 1)
+							canceledCounter.Add(1)
 							return
 						}
 						_, err = str.Write(PRData)
@@ -291,14 +291,14 @@ var _ = Describe("Stream Cancellations", func() {
 			}()
 
 			clientCanceledStreams := runClient(server)
-			Expect(clientCanceledStreams).To(Equal(atomic.LoadInt32(&canceledCounter)))
+			Expect(clientCanceledStreams).To(Equal(canceledCounter.Load()))
 		})
 
 		It("downloads when the server cancels some streams after sending some data", func() {
 			server, err := quic.ListenAddr("localhost:0", getTLSConfig(), nil)
 			Expect(err).ToNot(HaveOccurred())
 
-			var canceledCounter int32
+			var canceledCounter atomic.Int32
 			go func() {
 				defer GinkgoRecover()
 				conn, err := server.Accept(context.Background())
@@ -314,7 +314,7 @@ var _ = Describe("Stream Cancellations", func() {
 							_, err = str.Write(PRData[:length])
 							Expect(err).ToNot(HaveOccurred())
 							str.CancelWrite(quic.StreamErrorCode(str.StreamID()))
-							atomic.AddInt32(&canceledCounter, 1)
+							canceledCounter.Add(1)
 							return
 						}
 						_, err = str.Write(PRData)
@@ -325,7 +325,7 @@ var _ = Describe("Stream Cancellations", func() {
 			}()
 
 			clientCanceledStreams := runClient(server)
-			Expect(clientCanceledStreams).To(Equal(atomic.LoadInt32(&canceledCounter)))
+			Expect(clientCanceledStreams).To(Equal(canceledCounter.Load()))
 		})
 	})
 
@@ -378,7 +378,7 @@ var _ = Describe("Stream Cancellations", func() {
 			Expect(err).ToNot(HaveOccurred())
 
 			var wg sync.WaitGroup
-			var counter int32
+			var counter atomic.Int32
 			wg.Add(numStreams)
 			for i := 0; i < numStreams; i++ {
 				go func() {
@@ -399,13 +399,13 @@ var _ = Describe("Stream Cancellations", func() {
 						}))
 						return
 					}
-					atomic.AddInt32(&counter, 1)
+					counter.Add(1)
 					Expect(data).To(Equal(PRData))
 				}()
 			}
 			wg.Wait()
 
-			count := atomic.LoadInt32(&counter)
+			count := counter.Load()
 			Expect(count).To(BeNumerically(">", numStreams/15))
 			fmt.Fprintf(GinkgoWriter, "Successfully read from %d of %d streams.\n", count, numStreams)
 
@@ -464,7 +464,7 @@ var _ = Describe("Stream Cancellations", func() {
 			Expect(err).ToNot(HaveOccurred())
 
 			var wg sync.WaitGroup
-			var counter int32
+			var counter atomic.Int32
 			wg.Add(numStreams)
 			for i := 0; i < numStreams; i++ {
 				go func() {
@@ -495,14 +495,14 @@ var _ = Describe("Stream Cancellations", func() {
 						return
 					}
 
-					atomic.AddInt32(&counter, 1)
+					counter.Add(1)
 					Expect(data).To(Equal(PRData))
 				}()
 			}
 			wg.Wait()
 			Eventually(done).Should(BeClosed())
 
-			count := atomic.LoadInt32(&counter)
+			count := counter.Load()
 			Expect(count).To(BeNumerically(">", numStreams/15))
 			fmt.Fprintf(GinkgoWriter, "Successfully read from %d of %d streams.\n", count, numStreams)
 
@@ -543,7 +543,7 @@ var _ = Describe("Stream Cancellations", func() {
 			Expect(err).ToNot(HaveOccurred())
 
 			var numToAccept int
-			var counter int32
+			var counter atomic.Int32
 			var wg sync.WaitGroup
 			wg.Add(numStreams)
 			for numToAccept < numStreams {
@@ -561,7 +561,7 @@ var _ = Describe("Stream Cancellations", func() {
 					str, err := conn.AcceptUniStream(ctx)
 					if err != nil {
 						if err.Error() == "context canceled" {
-							atomic.AddInt32(&counter, 1)
+							counter.Add(1)
 						}
 						return
 					}
@@ -573,7 +573,7 @@ var _ = Describe("Stream Cancellations", func() {
 			}
 			wg.Wait()
 
-			count := atomic.LoadInt32(&counter)
+			count := counter.Load()
 			fmt.Fprintf(GinkgoWriter, "Canceled AcceptStream %d times\n", count)
 			Expect(count).To(BeNumerically(">", numStreams/2))
 			Expect(conn.CloseWithError(0, "")).To(Succeed())
@@ -589,7 +589,7 @@ var _ = Describe("Stream Cancellations", func() {
 			Expect(err).ToNot(HaveOccurred())
 
 			msg := make(chan struct{}, 1)
-			var numCanceled int32
+			var numCanceled atomic.Int32
 			go func() {
 				defer GinkgoRecover()
 				defer close(msg)
@@ -603,7 +603,7 @@ var _ = Describe("Stream Cancellations", func() {
 					str, err := conn.OpenUniStreamSync(ctx)
 					if err != nil {
 						Expect(err).To(MatchError(context.DeadlineExceeded))
-						atomic.AddInt32(&numCanceled, 1)
+						numCanceled.Add(1)
 						select {
 						case msg <- struct{}{}:
 						default:
@@ -644,7 +644,7 @@ var _ = Describe("Stream Cancellations", func() {
 			}
 			wg.Wait()
 
-			count := atomic.LoadInt32(&numCanceled)
+			count := numCanceled.Load()
 			fmt.Fprintf(GinkgoWriter, "Canceled OpenStreamSync %d times\n", count)
 			Expect(count).To(BeNumerically(">=", numStreams-maxIncomingStreams))
 			Expect(conn.CloseWithError(0, "")).To(Succeed())
diff --git a/integrationtests/self/datagram_test.go b/integrationtests/self/datagram_test.go
index 35d0718a..55e85771 100644
--- a/integrationtests/self/datagram_test.go
+++ b/integrationtests/self/datagram_test.go
@@ -23,7 +23,7 @@ var _ = Describe("Datagram test", func() {
 
 	var (
 		serverConn, clientConn *net.UDPConn
-		dropped, total         int32
+		dropped, total         atomic.Int32
 	)
 
 	startServerAndProxy := func(enableDatagram, expectDatagramSupport bool) (port int, closeFn func()) {
@@ -57,7 +57,7 @@ var _ = Describe("Datagram test", func() {
 							defer wg.Done()
 							b := make([]byte, 8)
 							binary.BigEndian.PutUint64(b, uint64(i))
-							Expect(conn.SendMessage(b)).To(Succeed())
+							Expect(conn.SendDatagram(b)).To(Succeed())
 						}(i)
 					}
 					wg.Wait()
@@ -81,9 +81,9 @@ var _ = Describe("Datagram test", func() {
 				}
 				drop := mrand.Int()%10 == 0
 				if drop {
-					atomic.AddInt32(&dropped, 1)
+					dropped.Add(1)
 				}
-				atomic.AddInt32(&total, 1)
+				total.Add(1)
 				return drop
 			},
 		})
@@ -120,16 +120,16 @@ var _ = Describe("Datagram test", func() {
 		for {
 			// Close the connection if no message is received for 100 ms.
 			timer := time.AfterFunc(scaleDuration(100*time.Millisecond), func() { conn.CloseWithError(0, "") })
-			if _, err := conn.ReceiveMessage(context.Background()); err != nil {
+			if _, err := conn.ReceiveDatagram(context.Background()); err != nil {
 				break
 			}
 			timer.Stop()
 			counter++
 		}
 
-		numDropped := int(atomic.LoadInt32(&dropped))
+		numDropped := int(dropped.Load())
 		expVal := num - numDropped
-		fmt.Fprintf(GinkgoWriter, "Dropped %d out of %d packets.\n", numDropped, atomic.LoadInt32(&total))
+		fmt.Fprintf(GinkgoWriter, "Dropped %d out of %d packets.\n", numDropped, total.Load())
 		fmt.Fprintf(GinkgoWriter, "Received %d out of %d sent datagrams.\n", counter, num)
 		Expect(counter).To(And(
 			BeNumerically(">", expVal*9/10),
@@ -170,7 +170,7 @@ var _ = Describe("Datagram test", func() {
 		Expect(err).ToNot(HaveOccurred())
 		Expect(conn.ConnectionState().SupportsDatagrams).To(BeFalse())
 
-		Expect(conn.SendMessage([]byte{0})).To(HaveOccurred())
+		Expect(conn.SendDatagram([]byte{0})).To(HaveOccurred())
 
 		close()
 		conn.CloseWithError(0, "")
diff --git a/integrationtests/self/drop_test.go b/integrationtests/self/drop_test.go
index 4eac657a..a3e385bb 100644
--- a/integrationtests/self/drop_test.go
+++ b/integrationtests/self/drop_test.go
@@ -67,14 +67,14 @@ var _ = Describe("Drop Tests", func() {
 				fmt.Fprintf(GinkgoWriter, "Dropping packets for %s, after a delay of %s\n", dropDuration, dropDelay)
 				startTime := time.Now()
 
-				var numDroppedPackets int32
+				var numDroppedPackets atomic.Int32
 				startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
 					if !d.Is(direction) {
 						return false
 					}
 					drop := time.Now().After(startTime.Add(dropDelay)) && time.Now().Before(startTime.Add(dropDelay).Add(dropDuration))
 					if drop {
-						atomic.AddInt32(&numDroppedPackets, 1)
+						numDroppedPackets.Add(1)
 					}
 					return drop
 				})
@@ -114,7 +114,7 @@ var _ = Describe("Drop Tests", func() {
 					Expect(b[0]).To(Equal(i))
 				}
 				close(done)
-				numDropped := atomic.LoadInt32(&numDroppedPackets)
+				numDropped := numDroppedPackets.Load()
 				fmt.Fprintf(GinkgoWriter, "Dropped %d packets.\n", numDropped)
 				Expect(numDropped).To(BeNumerically(">", 0))
 			})
diff --git a/integrationtests/self/handshake_drop_test.go b/integrationtests/self/handshake_drop_test.go
index ae483771..a0f23479 100644
--- a/integrationtests/self/handshake_drop_test.go
+++ b/integrationtests/self/handshake_drop_test.go
@@ -194,15 +194,15 @@ var _ = Describe("Handshake drop tests", func() {
 
 							Context(app.name, func() {
 								It(fmt.Sprintf("establishes a connection when the first packet is lost in %s direction", direction), func() {
-									var incoming, outgoing int32
+									var incoming, outgoing atomic.Int32
 									startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
 										var p int32
 										//nolint:exhaustive
 										switch d {
 										case quicproxy.DirectionIncoming:
-											p = atomic.AddInt32(&incoming, 1)
+											p = incoming.Add(1)
 										case quicproxy.DirectionOutgoing:
-											p = atomic.AddInt32(&outgoing, 1)
+											p = outgoing.Add(1)
 										}
 										return p == 1 && d.Is(direction)
 									}, doRetry, longCertChain)
@@ -210,15 +210,15 @@ var _ = Describe("Handshake drop tests", func() {
 								})
 
 								It(fmt.Sprintf("establishes a connection when the second packet is lost in %s direction", direction), func() {
-									var incoming, outgoing int32
+									var incoming, outgoing atomic.Int32
 									startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
 										var p int32
 										//nolint:exhaustive
 										switch d {
 										case quicproxy.DirectionIncoming:
-											p = atomic.AddInt32(&incoming, 1)
+											p = incoming.Add(1)
 										case quicproxy.DirectionOutgoing:
-											p = atomic.AddInt32(&outgoing, 1)
+											p = outgoing.Add(1)
 										}
 										return p == 2 && d.Is(direction)
 									}, doRetry, longCertChain)
diff --git a/integrationtests/self/hotswap_test.go b/integrationtests/self/hotswap_test.go
index ac75a494..232a13c3 100644
--- a/integrationtests/self/hotswap_test.go
+++ b/integrationtests/self/hotswap_test.go
@@ -20,7 +20,7 @@ import (
 type listenerWrapper struct {
 	http3.QUICEarlyListener
 	listenerClosed bool
-	count          int32
+	count          atomic.Int32
 }
 
 func (ln *listenerWrapper) Close() error {
@@ -29,14 +29,18 @@ func (ln *listenerWrapper) Close() error {
 }
 
 func (ln *listenerWrapper) Faker() *fakeClosingListener {
-	atomic.AddInt32(&ln.count, 1)
+	ln.count.Add(1)
 	ctx, cancel := context.WithCancel(context.Background())
-	return &fakeClosingListener{ln, 0, ctx, cancel}
+	return &fakeClosingListener{
+		listenerWrapper: ln,
+		ctx:             ctx,
+		cancel:          cancel,
+	}
 }
 
 type fakeClosingListener struct {
 	*listenerWrapper
-	closed int32
+	closed atomic.Bool
 	ctx    context.Context
 	cancel context.CancelFunc
 }
@@ -47,9 +51,9 @@ func (ln *fakeClosingListener) Accept(ctx context.Context) (quic.EarlyConnection
 }
 
 func (ln *fakeClosingListener) Close() error {
-	if atomic.CompareAndSwapInt32(&ln.closed, 0, 1) {
+	if ln.closed.CompareAndSwap(false, true) {
 		ln.cancel()
-		if atomic.AddInt32(&ln.listenerWrapper.count, -1) == 0 {
+		if ln.listenerWrapper.count.Add(-1) == 0 {
 			ln.listenerWrapper.Close()
 		}
 	}
@@ -145,8 +149,8 @@ var _ = Describe("HTTP3 Server hotswap test", func() {
 		// and only the fake listener should be closed
 		Expect(server1.Close()).NotTo(HaveOccurred())
 		Eventually(stoppedServing1).Should(BeClosed())
-		Expect(fake1.closed).To(Equal(int32(1)))
-		Expect(fake2.closed).To(Equal(int32(0)))
+		Expect(fake1.closed.Load()).To(BeTrue())
+		Expect(fake2.closed.Load()).To(BeFalse())
 		Expect(ln.listenerClosed).ToNot(BeTrue())
 		Expect(client.Transport.(*http3.RoundTripper).Close()).NotTo(HaveOccurred())
 
@@ -161,7 +165,7 @@ var _ = Describe("HTTP3 Server hotswap test", func() {
 		// close the other server - both the fake and the actual listeners must close now
 		Expect(server2.Close()).NotTo(HaveOccurred())
 		Eventually(stoppedServing2).Should(BeClosed())
-		Expect(fake2.closed).To(Equal(int32(1)))
+		Expect(fake2.closed.Load()).To(BeTrue())
 		Expect(ln.listenerClosed).To(BeTrue())
 	})
 })
diff --git a/integrationtests/self/mitm_test.go b/integrationtests/self/mitm_test.go
index a5f07f77..0eb68d06 100644
--- a/integrationtests/self/mitm_test.go
+++ b/integrationtests/self/mitm_test.go
@@ -244,17 +244,17 @@ var _ = Describe("MITM test", func() {
 		Context("corrupting packets", func() {
 			const idleTimeout = time.Second
 
-			var numCorrupted, numPackets int32
+			var numCorrupted, numPackets atomic.Int32
 
 			BeforeEach(func() {
-				numCorrupted = 0
-				numPackets = 0
+				numCorrupted.Store(0)
+				numPackets.Store(0)
 				serverConfig.MaxIdleTimeout = idleTimeout
 			})
 
 			AfterEach(func() {
-				num := atomic.LoadInt32(&numCorrupted)
-				fmt.Fprintf(GinkgoWriter, "Corrupted %d of %d packets.", num, atomic.LoadInt32(&numPackets))
+				num := numCorrupted.Load()
+				fmt.Fprintf(GinkgoWriter, "Corrupted %d of %d packets.", num, numPackets.Load())
 				Expect(num).To(BeNumerically(">=", 1))
 				// If the packet containing the CONNECTION_CLOSE is corrupted,
 				// we have to wait for the connection to time out.
@@ -266,13 +266,13 @@ var _ = Describe("MITM test", func() {
 				dropCb := func(dir quicproxy.Direction, raw []byte) bool {
 					defer GinkgoRecover()
 					if dir == quicproxy.DirectionIncoming {
-						atomic.AddInt32(&numPackets, 1)
+						numPackets.Add(1)
 						if rand.Intn(interval) == 0 {
 							pos := rand.Intn(len(raw))
 							raw[pos] = byte(rand.Intn(256))
 							_, err := clientTransport.WriteTo(raw, serverTransport.Conn.LocalAddr())
 							Expect(err).ToNot(HaveOccurred())
-							atomic.AddInt32(&numCorrupted, 1)
+							numCorrupted.Add(1)
 							return true
 						}
 					}
@@ -286,13 +286,13 @@ var _ = Describe("MITM test", func() {
 				dropCb := func(dir quicproxy.Direction, raw []byte) bool {
 					defer GinkgoRecover()
 					if dir == quicproxy.DirectionOutgoing {
-						atomic.AddInt32(&numPackets, 1)
+						numPackets.Add(1)
 						if rand.Intn(interval) == 0 {
 							pos := rand.Intn(len(raw))
 							raw[pos] = byte(rand.Intn(256))
 							_, err := serverTransport.WriteTo(raw, clientTransport.Conn.LocalAddr())
 							Expect(err).ToNot(HaveOccurred())
-							atomic.AddInt32(&numCorrupted, 1)
+							numCorrupted.Add(1)
 							return true
 						}
 					}
diff --git a/integrationtests/self/timeout_test.go b/integrationtests/self/timeout_test.go
index ddd0b973..dd18c4ea 100644
--- a/integrationtests/self/timeout_test.go
+++ b/integrationtests/self/timeout_test.go
@@ -22,12 +22,12 @@ type faultyConn struct {
 	net.PacketConn
 
 	MaxPackets int32
-	counter    int32
+	counter    atomic.Int32
 }
 
 func (c *faultyConn) ReadFrom(p []byte) (int, net.Addr, error) {
 	n, addr, err := c.PacketConn.ReadFrom(p)
-	counter := atomic.AddInt32(&c.counter, 1)
+	counter := c.counter.Add(1)
 	if counter <= c.MaxPackets {
 		return n, addr, err
 	}
@@ -35,7 +35,7 @@ func (c *faultyConn) ReadFrom(p []byte) (int, net.Addr, error) {
 }
 
 func (c *faultyConn) WriteTo(p []byte, addr net.Addr) (int, error) {
-	counter := atomic.AddInt32(&c.counter, 1)
+	counter := c.counter.Add(1)
 	if counter <= c.MaxPackets {
 		return c.PacketConn.WriteTo(p, addr)
 	}
diff --git a/integrationtests/self/tracer_test.go b/integrationtests/self/tracer_test.go
index 5179646e..21691661 100644
--- a/integrationtests/self/tracer_test.go
+++ b/integrationtests/self/tracer_test.go
@@ -19,7 +19,7 @@ import (
 	. "github.com/onsi/gomega"
 )
 
-var _ = Describe("Handshake tests", func() {
+var _ = Describe("Tracer tests", func() {
 	addTracers := func(pers protocol.Perspective, conf *quic.Config) *quic.Config {
 		enableQlog := mrand.Int()%3 != 0
 		enableCustomTracer := mrand.Int()%3 != 0
@@ -30,10 +30,10 @@ var _ = Describe("Handshake tests", func() {
 		if enableQlog {
 			tracerConstructors = append(tracerConstructors, func(_ context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer {
 				if mrand.Int()%2 == 0 { // simulate that a qlog collector might only want to log some connections
-					fmt.Fprintf(GinkgoWriter, "%s qlog tracer deciding to not trace connection %x\n", p, connID)
+					fmt.Fprintf(GinkgoWriter, "%s qlog tracer deciding to not trace connection %s\n", p, connID)
 					return nil
 				}
-				fmt.Fprintf(GinkgoWriter, "%s qlog tracing connection %x\n", p, connID)
+				fmt.Fprintf(GinkgoWriter, "%s qlog tracing connection %s\n", p, connID)
 				return qlog.NewConnectionTracer(utils.NewBufferedWriteCloser(bufio.NewWriter(&bytes.Buffer{}), io.NopCloser(nil)), p, connID)
 			})
 		}
diff --git a/integrationtests/self/zero_rtt_oldgo_test.go b/integrationtests/self/zero_rtt_oldgo_test.go
index eb2302d3..8d07ea25 100644
--- a/integrationtests/self/zero_rtt_oldgo_test.go
+++ b/integrationtests/self/zero_rtt_oldgo_test.go
@@ -26,8 +26,8 @@ import (
 var _ = Describe("0-RTT", func() {
 	rtt := scaleDuration(5 * time.Millisecond)
 
-	runCountingProxy := func(serverPort int) (*quicproxy.QuicProxy, *uint32) {
-		var num0RTTPackets uint32 // to be used as an atomic
+	runCountingProxy := func(serverPort int) (*quicproxy.QuicProxy, *atomic.Uint32) {
+		var num0RTTPackets atomic.Uint32
 		proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
 			RemoteAddr: fmt.Sprintf("localhost:%d", serverPort),
 			DelayPacket: func(_ quicproxy.Direction, data []byte) time.Duration {
@@ -38,7 +38,7 @@ var _ = Describe("0-RTT", func() {
 					hdr, _, rest, err := wire.ParsePacket(data)
 					Expect(err).ToNot(HaveOccurred())
 					if hdr.Type == protocol.PacketType0RTT {
-						atomic.AddUint32(&num0RTTPackets, 1)
+						num0RTTPackets.Add(1)
 						break
 					}
 					data = rest
@@ -257,7 +257,7 @@ var _ = Describe("0-RTT", func() {
 				Expect(numNewConnIDs).ToNot(BeZero())
 			}
 
-			num0RTT := atomic.LoadUint32(num0RTTPackets)
+			num0RTT := num0RTTPackets.Load()
 			fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
 			Expect(num0RTT).ToNot(BeZero())
 			zeroRTTPackets := get0RTTPackets(counter.getRcvdLongHeaderPackets())
@@ -348,10 +348,7 @@ var _ = Describe("0-RTT", func() {
 	})
 
 	It("transfers 0-RTT data, when 0-RTT packets are lost", func() {
-		var (
-			num0RTTPackets uint32 // to be used as an atomic
-			num0RTTDropped uint32
-		)
+		var num0RTTPackets, num0RTTDropped atomic.Uint32
 
 		tlsConf, clientConf := dialAndReceiveSessionTicket(nil)
 
@@ -374,7 +371,7 @@ var _ = Describe("0-RTT", func() {
 					hdr, _, _, err := wire.ParsePacket(data)
 					Expect(err).ToNot(HaveOccurred())
 					if hdr.Type == protocol.PacketType0RTT {
-						atomic.AddUint32(&num0RTTPackets, 1)
+						num0RTTPackets.Add(1)
 					}
 				}
 				return rtt / 2
@@ -389,7 +386,7 @@ var _ = Describe("0-RTT", func() {
 					// drop 25% of the 0-RTT packets
 					drop := mrand.Intn(4) == 0
 					if drop {
-						atomic.AddUint32(&num0RTTDropped, 1)
+						num0RTTDropped.Add(1)
 					}
 					return drop
 				}
@@ -401,8 +398,8 @@ var _ = Describe("0-RTT", func() {
 
 		transfer0RTTData(ln, proxy.LocalPort(), protocol.DefaultConnectionIDLength, clientConf, nil, PRData)
 
-		num0RTT := atomic.LoadUint32(&num0RTTPackets)
-		numDropped := atomic.LoadUint32(&num0RTTDropped)
+		num0RTT := num0RTTPackets.Load()
+		numDropped := num0RTTDropped.Load()
 		fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets. Dropped %d of those.", num0RTT, numDropped)
 		Expect(numDropped).ToNot(BeZero())
 		Expect(num0RTT).ToNot(BeZero())
@@ -551,7 +548,7 @@ var _ = Describe("0-RTT", func() {
 		check0RTTRejected(ln, proxy.LocalPort(), clientConf)
 
 		// The client should send 0-RTT packets, but the server doesn't process them.
-		num0RTT := atomic.LoadUint32(num0RTTPackets)
+		num0RTT := num0RTTPackets.Load()
 		fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
 		Expect(num0RTT).ToNot(BeZero())
 		Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
@@ -580,7 +577,7 @@ var _ = Describe("0-RTT", func() {
 		check0RTTRejected(ln, proxy.LocalPort(), clientConf)
 
 		// The client should send 0-RTT packets, but the server doesn't process them.
-		num0RTT := atomic.LoadUint32(num0RTTPackets)
+		num0RTT := num0RTTPackets.Load()
 		fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
 		Expect(num0RTT).ToNot(BeZero())
 		Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
@@ -607,7 +604,7 @@ var _ = Describe("0-RTT", func() {
 		check0RTTRejected(ln, proxy.LocalPort(), clientConf)
 
 		// The client should send 0-RTT packets, but the server doesn't process them.
-		num0RTT := atomic.LoadUint32(num0RTTPackets)
+		num0RTT := num0RTTPackets.Load()
 		fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
 		Expect(num0RTT).ToNot(BeZero())
 		Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
@@ -759,7 +756,7 @@ var _ = Describe("0-RTT", func() {
 			Expect(conn.CloseWithError(0, "")).To(Succeed())
 
 			// The client should send 0-RTT packets, but the server doesn't process them.
-			num0RTT := atomic.LoadUint32(num0RTTPackets)
+			num0RTT := num0RTTPackets.Load()
 			fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
 			Expect(num0RTT).ToNot(BeZero())
 			Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
@@ -830,7 +827,7 @@ var _ = Describe("0-RTT", func() {
 			defer close(received)
 			conn, err := ln.Accept(context.Background())
 			Expect(err).ToNot(HaveOccurred())
-			receivedMessage, err = conn.ReceiveMessage(context.Background())
+			receivedMessage, err = conn.ReceiveDatagram(context.Background())
 			Expect(err).ToNot(HaveOccurred())
 			Expect(conn.ConnectionState().Used0RTT).To(BeTrue())
 		}()
@@ -844,14 +841,14 @@ var _ = Describe("0-RTT", func() {
 		)
 		Expect(err).ToNot(HaveOccurred())
 		Expect(conn.ConnectionState().SupportsDatagrams).To(BeTrue())
-		Expect(conn.SendMessage(sentMessage)).To(Succeed())
+		Expect(conn.SendDatagram(sentMessage)).To(Succeed())
 		<-conn.HandshakeComplete()
 		<-received
 
 		Expect(conn.ConnectionState().Used0RTT).To(BeTrue())
 		Expect(receivedMessage).To(Equal(sentMessage))
 		Expect(conn.CloseWithError(0, "")).To(Succeed())
-		num0RTT := atomic.LoadUint32(num0RTTPackets)
+		num0RTT := num0RTTPackets.Load()
 		fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
 		Expect(num0RTT).ToNot(BeZero())
 		zeroRTTPackets := get0RTTPackets(counter.getRcvdLongHeaderPackets())
@@ -884,7 +881,7 @@ var _ = Describe("0-RTT", func() {
 			defer GinkgoRecover()
 			conn, err := ln.Accept(context.Background())
 			Expect(err).ToNot(HaveOccurred())
-			_, err = conn.ReceiveMessage(context.Background())
+			_, err = conn.ReceiveDatagram(context.Background())
 			Expect(err.Error()).To(Equal("datagram support disabled"))
 			<-conn.HandshakeComplete()
 			Expect(conn.ConnectionState().Used0RTT).To(BeFalse())
@@ -900,13 +897,13 @@ var _ = Describe("0-RTT", func() {
 		Expect(err).ToNot(HaveOccurred())
 		// the client can temporarily send datagrams but the server doesn't process them.
 		Expect(conn.ConnectionState().SupportsDatagrams).To(BeTrue())
-		Expect(conn.SendMessage(make([]byte, 100))).To(Succeed())
+		Expect(conn.SendDatagram(make([]byte, 100))).To(Succeed())
 		<-conn.HandshakeComplete()
 
 		Expect(conn.ConnectionState().SupportsDatagrams).To(BeFalse())
 		Expect(conn.ConnectionState().Used0RTT).To(BeFalse())
 		Expect(conn.CloseWithError(0, "")).To(Succeed())
-		num0RTT := atomic.LoadUint32(num0RTTPackets)
+		num0RTT := num0RTTPackets.Load()
 		fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
 		Expect(num0RTT).ToNot(BeZero())
 		Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
diff --git a/integrationtests/self/zero_rtt_test.go b/integrationtests/self/zero_rtt_test.go
index 1f750d3a..7f8184a2 100644
--- a/integrationtests/self/zero_rtt_test.go
+++ b/integrationtests/self/zero_rtt_test.go
@@ -56,8 +56,8 @@ func (m metadataClientSessionCache) Put(key string, session *tls.ClientSessionSt
 var _ = Describe("0-RTT", func() {
 	rtt := scaleDuration(5 * time.Millisecond)
 
-	runCountingProxy := func(serverPort int) (*quicproxy.QuicProxy, *uint32) {
-		var num0RTTPackets uint32 // to be used as an atomic
+	runCountingProxy := func(serverPort int) (*quicproxy.QuicProxy, *atomic.Uint32) {
+		var num0RTTPackets atomic.Uint32
 		proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
 			RemoteAddr: fmt.Sprintf("localhost:%d", serverPort),
 			DelayPacket: func(_ quicproxy.Direction, data []byte) time.Duration {
@@ -68,7 +68,7 @@ var _ = Describe("0-RTT", func() {
 					hdr, _, rest, err := wire.ParsePacket(data)
 					Expect(err).ToNot(HaveOccurred())
 					if hdr.Type == protocol.PacketType0RTT {
-						atomic.AddUint32(&num0RTTPackets, 1)
+						num0RTTPackets.Add(1)
 						break
 					}
 					data = rest
@@ -289,7 +289,7 @@ var _ = Describe("0-RTT", func() {
 				Expect(numNewConnIDs).ToNot(BeZero())
 			}
 
-			num0RTT := atomic.LoadUint32(num0RTTPackets)
+			num0RTT := num0RTTPackets.Load()
 			fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
 			Expect(num0RTT).ToNot(BeZero())
 			zeroRTTPackets := get0RTTPackets(counter.getRcvdLongHeaderPackets())
@@ -382,10 +382,7 @@ var _ = Describe("0-RTT", func() {
 	})
 
 	It("transfers 0-RTT data, when 0-RTT packets are lost", func() {
-		var (
-			num0RTTPackets uint32 // to be used as an atomic
-			num0RTTDropped uint32
-		)
+		var num0RTTPackets, num0RTTDropped atomic.Uint32
 
 		tlsConf := getTLSConfig()
 		clientConf := getTLSClientConfig()
@@ -410,7 +407,7 @@ var _ = Describe("0-RTT", func() {
 					hdr, _, _, err := wire.ParsePacket(data)
 					Expect(err).ToNot(HaveOccurred())
 					if hdr.Type == protocol.PacketType0RTT {
-						atomic.AddUint32(&num0RTTPackets, 1)
+						num0RTTPackets.Add(1)
 					}
 				}
 				return rtt / 2
@@ -425,7 +422,7 @@ var _ = Describe("0-RTT", func() {
 					// drop 25% of the 0-RTT packets
 					drop := mrand.Intn(4) == 0
 					if drop {
-						atomic.AddUint32(&num0RTTDropped, 1)
+						num0RTTDropped.Add(1)
 					}
 					return drop
 				}
@@ -437,8 +434,8 @@ var _ = Describe("0-RTT", func() {
 
 		transfer0RTTData(ln, proxy.LocalPort(), protocol.DefaultConnectionIDLength, clientConf, nil, PRData)
 
-		num0RTT := atomic.LoadUint32(&num0RTTPackets)
-		numDropped := atomic.LoadUint32(&num0RTTDropped)
+		num0RTT := num0RTTPackets.Load()
+		numDropped := num0RTTDropped.Load()
 		fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets. Dropped %d of those.", num0RTT, numDropped)
 		Expect(numDropped).ToNot(BeZero())
 		Expect(num0RTT).ToNot(BeZero())
@@ -594,7 +591,7 @@ var _ = Describe("0-RTT", func() {
 		check0RTTRejected(ln, proxy.LocalPort(), clientConf)
 
 		// The client should send 0-RTT packets, but the server doesn't process them.
-		num0RTT := atomic.LoadUint32(num0RTTPackets)
+		num0RTT := num0RTTPackets.Load()
 		fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
 		Expect(num0RTT).ToNot(BeZero())
 		Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
@@ -627,7 +624,7 @@ var _ = Describe("0-RTT", func() {
 		check0RTTRejected(ln, proxy.LocalPort(), clientConf)
 
 		// The client should send 0-RTT packets, but the server doesn't process them.
-		num0RTT := atomic.LoadUint32(num0RTTPackets)
+		num0RTT := num0RTTPackets.Load()
 		fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
 		Expect(num0RTT).ToNot(BeZero())
 		Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
@@ -656,7 +653,7 @@ var _ = Describe("0-RTT", func() {
 		check0RTTRejected(ln, proxy.LocalPort(), clientConf)
 
 		// The client should send 0-RTT packets, but the server doesn't process them.
-		num0RTT := atomic.LoadUint32(num0RTTPackets)
+		num0RTT := num0RTTPackets.Load()
 		fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
 		Expect(num0RTT).ToNot(BeZero())
 		Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
@@ -812,7 +809,7 @@ var _ = Describe("0-RTT", func() {
 			Expect(conn.CloseWithError(0, "")).To(Succeed())
 
 			// The client should send 0-RTT packets, but the server doesn't process them.
-			num0RTT := atomic.LoadUint32(num0RTTPackets)
+			num0RTT := num0RTTPackets.Load()
 			fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
 			Expect(num0RTT).ToNot(BeZero())
 			Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
@@ -960,7 +957,7 @@ var _ = Describe("0-RTT", func() {
 			defer close(received)
 			conn, err := ln.Accept(context.Background())
 			Expect(err).ToNot(HaveOccurred())
-			receivedMessage, err = conn.ReceiveMessage(context.Background())
+			receivedMessage, err = conn.ReceiveDatagram(context.Background())
 			Expect(err).ToNot(HaveOccurred())
 			Expect(conn.ConnectionState().Used0RTT).To(BeTrue())
 		}()
@@ -974,14 +971,14 @@ var _ = Describe("0-RTT", func() {
 		)
 		Expect(err).ToNot(HaveOccurred())
 		Expect(conn.ConnectionState().SupportsDatagrams).To(BeTrue())
-		Expect(conn.SendMessage(sentMessage)).To(Succeed())
+		Expect(conn.SendDatagram(sentMessage)).To(Succeed())
 		<-conn.HandshakeComplete()
 		<-received
 
 		Expect(conn.ConnectionState().Used0RTT).To(BeTrue())
 		Expect(conn.CloseWithError(0, "")).To(Succeed())
 		Expect(receivedMessage).To(Equal(sentMessage))
-		num0RTT := atomic.LoadUint32(num0RTTPackets)
+		num0RTT := num0RTTPackets.Load()
 		fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
 		Expect(num0RTT).ToNot(BeZero())
 		zeroRTTPackets := get0RTTPackets(counter.getRcvdLongHeaderPackets())
@@ -1016,7 +1013,7 @@ var _ = Describe("0-RTT", func() {
 			defer GinkgoRecover()
 			conn, err := ln.Accept(context.Background())
 			Expect(err).ToNot(HaveOccurred())
-			_, err = conn.ReceiveMessage(context.Background())
+			_, err = conn.ReceiveDatagram(context.Background())
 			Expect(err.Error()).To(Equal("datagram support disabled"))
 			<-conn.HandshakeComplete()
 			Expect(conn.ConnectionState().Used0RTT).To(BeFalse())
@@ -1032,13 +1029,13 @@ var _ = Describe("0-RTT", func() {
 		Expect(err).ToNot(HaveOccurred())
 		// the client can temporarily send datagrams but the server doesn't process them.
 		Expect(conn.ConnectionState().SupportsDatagrams).To(BeTrue())
-		Expect(conn.SendMessage(make([]byte, 100))).To(Succeed())
+		Expect(conn.SendDatagram(make([]byte, 100))).To(Succeed())
 		<-conn.HandshakeComplete()
 
 		Expect(conn.ConnectionState().SupportsDatagrams).To(BeFalse())
 		Expect(conn.ConnectionState().Used0RTT).To(BeFalse())
 		Expect(conn.CloseWithError(0, "")).To(Succeed())
-		num0RTT := atomic.LoadUint32(num0RTTPackets)
+		num0RTT := num0RTTPackets.Load()
 		fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
 		Expect(num0RTT).ToNot(BeZero())
 		Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
diff --git a/integrationtests/tools/proxy/proxy_test.go b/integrationtests/tools/proxy/proxy_test.go
index c9b80be5..c51a436a 100644
--- a/integrationtests/tools/proxy/proxy_test.go
+++ b/integrationtests/tools/proxy/proxy_test.go
@@ -141,7 +141,7 @@ var _ = Describe("QUIC Proxy", func() {
 	Context("Proxy tests", func() {
 		var (
 			serverConn            *net.UDPConn
-			serverNumPacketsSent  int32
+			serverNumPacketsSent  atomic.Int32
 			serverReceivedPackets chan packetData
 			clientConn            *net.UDPConn
 			proxy                 *QuicProxy
@@ -159,9 +159,9 @@ var _ = Describe("QUIC Proxy", func() {
 		BeforeEach(func() {
 			stoppedReading = make(chan struct{})
 			serverReceivedPackets = make(chan packetData, 100)
-			atomic.StoreInt32(&serverNumPacketsSent, 0)
+			serverNumPacketsSent.Store(0)
 
-			// setup a dump UDP server
+			// set up a dump UDP server
 			// in production this would be a QUIC server
 			raddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
 			Expect(err).ToNot(HaveOccurred())
@@ -181,7 +181,7 @@ var _ = Describe("QUIC Proxy", func() {
 					data := buf[0:n]
 					serverReceivedPackets <- packetData(data)
 					// echo the packet
-					atomic.AddInt32(&serverNumPacketsSent, 1)
+					serverNumPacketsSent.Add(1)
 					serverConn.WriteToUDP(data, addr)
 				}
 			}()
@@ -236,7 +236,7 @@ var _ = Describe("QUIC Proxy", func() {
 				}()
 
 				Eventually(serverReceivedPackets).Should(HaveLen(2))
-				Expect(atomic.LoadInt32(&serverNumPacketsSent)).To(BeEquivalentTo(2))
+				Expect(serverNumPacketsSent.Load()).To(BeEquivalentTo(2))
 				Eventually(clientReceivedPackets).Should(HaveLen(2))
 				Expect(string(<-clientReceivedPackets)).To(ContainSubstring("foobar"))
 				Expect(string(<-clientReceivedPackets)).To(ContainSubstring("decafbad"))
@@ -245,14 +245,14 @@ var _ = Describe("QUIC Proxy", func() {
 
 		Context("Drop Callbacks", func() {
 			It("drops incoming packets", func() {
-				var counter int32
+				var counter atomic.Int32
 				opts := &Opts{
 					RemoteAddr: serverConn.LocalAddr().String(),
 					DropPacket: func(d Direction, _ []byte) bool {
 						if d != DirectionIncoming {
 							return false
 						}
-						return atomic.AddInt32(&counter, 1)%2 == 1
+						return counter.Add(1)%2 == 1
 					},
 				}
 				startProxy(opts)
@@ -267,14 +267,14 @@ var _ = Describe("QUIC Proxy", func() {
 
 			It("drops outgoing packets", func() {
 				const numPackets = 6
-				var counter int32
+				var counter atomic.Int32
 				opts := &Opts{
 					RemoteAddr: serverConn.LocalAddr().String(),
 					DropPacket: func(d Direction, _ []byte) bool {
 						if d != DirectionOutgoing {
 							return false
 						}
-						return atomic.AddInt32(&counter, 1)%2 == 1
+						return counter.Add(1)%2 == 1
 					},
 				}
 				startProxy(opts)
@@ -315,7 +315,7 @@ var _ = Describe("QUIC Proxy", func() {
 			}
 
 			It("delays incoming packets", func() {
-				var counter int32
+				var counter atomic.Int32
 				opts := &Opts{
 					RemoteAddr: serverConn.LocalAddr().String(),
 					// delay packet 1 by 200 ms
@@ -325,7 +325,7 @@ var _ = Describe("QUIC Proxy", func() {
 						if d == DirectionOutgoing {
 							return 0
 						}
-						p := atomic.AddInt32(&counter, 1)
+						p := counter.Add(1)
 						return time.Duration(p) * delay
 					},
 				}
@@ -349,7 +349,7 @@ var _ = Describe("QUIC Proxy", func() {
 			})
 
 			It("handles reordered packets", func() {
-				var counter int32
+				var counter atomic.Int32
 				opts := &Opts{
 					RemoteAddr: serverConn.LocalAddr().String(),
 					// delay packet 1 by 600 ms
@@ -359,7 +359,7 @@ var _ = Describe("QUIC Proxy", func() {
 						if d == DirectionOutgoing {
 							return 0
 						}
-						p := atomic.AddInt32(&counter, 1)
+						p := counter.Add(1)
 						return 600*time.Millisecond - time.Duration(p-1)*delay
 					},
 				}
@@ -407,7 +407,7 @@ var _ = Describe("QUIC Proxy", func() {
 
 			It("delays outgoing packets", func() {
 				const numPackets = 3
-				var counter int32
+				var counter atomic.Int32
 				opts := &Opts{
 					RemoteAddr: serverConn.LocalAddr().String(),
 					// delay packet 1 by 200 ms
@@ -417,7 +417,7 @@ var _ = Describe("QUIC Proxy", func() {
 						if d == DirectionIncoming {
 							return 0
 						}
-						p := atomic.AddInt32(&counter, 1)
+						p := counter.Add(1)
 						return time.Duration(p) * delay
 					},
 				}
diff --git a/mock_quic_conn_test.go b/mock_quic_conn_test.go
index c7d84850..2a677066 100644
--- a/mock_quic_conn_test.go
+++ b/mock_quic_conn_test.go
@@ -541,41 +541,41 @@ func (c *QUICConnOpenUniStreamSyncCall) DoAndReturn(f func(context.Context) (Sen
 	return c
 }
 
-// ReceiveMessage mocks base method.
-func (m *MockQUICConn) ReceiveMessage(arg0 context.Context) ([]byte, error) {
+// ReceiveDatagram mocks base method.
+func (m *MockQUICConn) ReceiveDatagram(arg0 context.Context) ([]byte, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "ReceiveMessage", arg0)
+	ret := m.ctrl.Call(m, "ReceiveDatagram", arg0)
 	ret0, _ := ret[0].([]byte)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
-// ReceiveMessage indicates an expected call of ReceiveMessage.
-func (mr *MockQUICConnMockRecorder) ReceiveMessage(arg0 any) *QUICConnReceiveMessageCall {
+// ReceiveDatagram indicates an expected call of ReceiveDatagram.
+func (mr *MockQUICConnMockRecorder) ReceiveDatagram(arg0 any) *QUICConnReceiveDatagramCall {
 	mr.mock.ctrl.T.Helper()
-	call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReceiveMessage", reflect.TypeOf((*MockQUICConn)(nil).ReceiveMessage), arg0)
-	return &QUICConnReceiveMessageCall{Call: call}
+	call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReceiveDatagram", reflect.TypeOf((*MockQUICConn)(nil).ReceiveDatagram), arg0)
+	return &QUICConnReceiveDatagramCall{Call: call}
 }
 
-// QUICConnReceiveMessageCall wrap *gomock.Call
-type QUICConnReceiveMessageCall struct {
+// QUICConnReceiveDatagramCall wrap *gomock.Call
+type QUICConnReceiveDatagramCall struct {
 	*gomock.Call
 }
 
 // Return rewrite *gomock.Call.Return
-func (c *QUICConnReceiveMessageCall) Return(arg0 []byte, arg1 error) *QUICConnReceiveMessageCall {
+func (c *QUICConnReceiveDatagramCall) Return(arg0 []byte, arg1 error) *QUICConnReceiveDatagramCall {
 	c.Call = c.Call.Return(arg0, arg1)
 	return c
 }
 
 // Do rewrite *gomock.Call.Do
-func (c *QUICConnReceiveMessageCall) Do(f func(context.Context) ([]byte, error)) *QUICConnReceiveMessageCall {
+func (c *QUICConnReceiveDatagramCall) Do(f func(context.Context) ([]byte, error)) *QUICConnReceiveDatagramCall {
 	c.Call = c.Call.Do(f)
 	return c
 }
 
 // DoAndReturn rewrite *gomock.Call.DoAndReturn
-func (c *QUICConnReceiveMessageCall) DoAndReturn(f func(context.Context) ([]byte, error)) *QUICConnReceiveMessageCall {
+func (c *QUICConnReceiveDatagramCall) DoAndReturn(f func(context.Context) ([]byte, error)) *QUICConnReceiveDatagramCall {
 	c.Call = c.Call.DoAndReturn(f)
 	return c
 }
@@ -618,40 +618,40 @@ func (c *QUICConnRemoteAddrCall) DoAndReturn(f func() net.Addr) *QUICConnRemoteA
 	return c
 }
 
-// SendMessage mocks base method.
-func (m *MockQUICConn) SendMessage(arg0 []byte) error {
+// SendDatagram mocks base method.
+func (m *MockQUICConn) SendDatagram(arg0 []byte) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "SendMessage", arg0)
+	ret := m.ctrl.Call(m, "SendDatagram", arg0)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
-// SendMessage indicates an expected call of SendMessage.
-func (mr *MockQUICConnMockRecorder) SendMessage(arg0 any) *QUICConnSendMessageCall {
+// SendDatagram indicates an expected call of SendDatagram.
+func (mr *MockQUICConnMockRecorder) SendDatagram(arg0 any) *QUICConnSendDatagramCall {
 	mr.mock.ctrl.T.Helper()
-	call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockQUICConn)(nil).SendMessage), arg0)
-	return &QUICConnSendMessageCall{Call: call}
+	call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendDatagram", reflect.TypeOf((*MockQUICConn)(nil).SendDatagram), arg0)
+	return &QUICConnSendDatagramCall{Call: call}
 }
 
-// QUICConnSendMessageCall wrap *gomock.Call
-type QUICConnSendMessageCall struct {
+// QUICConnSendDatagramCall wrap *gomock.Call
+type QUICConnSendDatagramCall struct {
 	*gomock.Call
 }
 
 // Return rewrite *gomock.Call.Return
-func (c *QUICConnSendMessageCall) Return(arg0 error) *QUICConnSendMessageCall {
+func (c *QUICConnSendDatagramCall) Return(arg0 error) *QUICConnSendDatagramCall {
 	c.Call = c.Call.Return(arg0)
 	return c
 }
 
 // Do rewrite *gomock.Call.Do
-func (c *QUICConnSendMessageCall) Do(f func([]byte) error) *QUICConnSendMessageCall {
+func (c *QUICConnSendDatagramCall) Do(f func([]byte) error) *QUICConnSendDatagramCall {
 	c.Call = c.Call.Do(f)
 	return c
 }
 
 // DoAndReturn rewrite *gomock.Call.DoAndReturn
-func (c *QUICConnSendMessageCall) DoAndReturn(f func([]byte) error) *QUICConnSendMessageCall {
+func (c *QUICConnSendDatagramCall) DoAndReturn(f func([]byte) error) *QUICConnSendDatagramCall {
 	c.Call = c.Call.DoAndReturn(f)
 	return c
 }