From 3c0726e132ccf22b3434ee3e6ac4e4a7955bbe11 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Wed, 17 Feb 2021 11:34:19 +0800 Subject: [PATCH 1/3] move the random number generator to the utils package --- .../ackhandler/packet_number_generator.go | 27 +--------------- internal/utils/rand.go | 29 +++++++++++++++++ internal/utils/rand_test.go | 32 +++++++++++++++++++ 3 files changed, 62 insertions(+), 26 deletions(-) create mode 100644 internal/utils/rand.go create mode 100644 internal/utils/rand_test.go diff --git a/internal/ackhandler/packet_number_generator.go b/internal/ackhandler/packet_number_generator.go index adb39fcdab3..7d58650cc8a 100644 --- a/internal/ackhandler/packet_number_generator.go +++ b/internal/ackhandler/packet_number_generator.go @@ -1,9 +1,6 @@ package ackhandler import ( - "crypto/rand" - "encoding/binary" - "github.com/lucas-clemente/quic-go/internal/protocol" "github.com/lucas-clemente/quic-go/internal/utils" ) @@ -33,28 +30,6 @@ func (p *sequentialPacketNumberGenerator) Pop() protocol.PacketNumber { return next } -type rng struct { - buf [4]byte -} - -func (r *rng) Int31() int32 { - rand.Read(r.buf[:]) - return int32(binary.BigEndian.Uint32(r.buf[:]) & ^uint32(1<<31)) -} - -// copied from the standard library math/rand implementation of Int63n -func (r *rng) Int31n(n int32) int32 { - if n&(n-1) == 0 { // n is power of two, can mask - return r.Int31() & (n - 1) - } - max := int32((1 << 31) - 1 - (1<<31)%uint32(n)) - v := r.Int31() - for v > max { - v = r.Int31() - } - return v % n -} - // The skippingPacketNumberGenerator generates the packet number for the next packet // it randomly skips a packet number every averagePeriod packets (on average). // It is guaranteed to never skip two consecutive packet numbers. @@ -65,7 +40,7 @@ type skippingPacketNumberGenerator struct { next protocol.PacketNumber nextToSkip protocol.PacketNumber - rng rng + rng utils.Rand } var _ packetNumberGenerator = &skippingPacketNumberGenerator{} diff --git a/internal/utils/rand.go b/internal/utils/rand.go new file mode 100644 index 00000000000..30069144a20 --- /dev/null +++ b/internal/utils/rand.go @@ -0,0 +1,29 @@ +package utils + +import ( + "crypto/rand" + "encoding/binary" +) + +// Rand is a wrapper around crypto/rand that adds some convenience functions known from math/rand. +type Rand struct { + buf [4]byte +} + +func (r *Rand) Int31() int32 { + rand.Read(r.buf[:]) + return int32(binary.BigEndian.Uint32(r.buf[:]) & ^uint32(1<<31)) +} + +// copied from the standard library math/rand implementation of Int63n +func (r *Rand) Int31n(n int32) int32 { + if n&(n-1) == 0 { // n is power of two, can mask + return r.Int31() & (n - 1) + } + max := int32((1 << 31) - 1 - (1<<31)%uint32(n)) + v := r.Int31() + for v > max { + v = r.Int31() + } + return v % n +} diff --git a/internal/utils/rand_test.go b/internal/utils/rand_test.go new file mode 100644 index 00000000000..4e865c371f8 --- /dev/null +++ b/internal/utils/rand_test.go @@ -0,0 +1,32 @@ +package utils + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Rand", func() { + It("generates random numbers", func() { + const ( + num = 1000 + max = 123456 + ) + + var values [num]int32 + var r Rand + for i := 0; i < num; i++ { + v := r.Int31n(max) + Expect(v).To(And( + BeNumerically(">=", 0), + BeNumerically("<", max), + )) + values[i] = v + } + + var sum uint64 + for _, n := range values { + sum += uint64(n) + } + Expect(float64(sum) / num).To(BeNumerically("~", max/2, max/25)) + }) +}) From 115fc28bbe3667b30b3919ec03e4869a33fcc1db Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Wed, 17 Feb 2021 11:39:49 +0800 Subject: [PATCH 2/3] avoid initializing a math/rand.Rand in the connIDManager math/rand.Source uses a lot of memory, as it keeps an array of 607 int64s as internal state. --- conn_id_manager.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/conn_id_manager.go b/conn_id_manager.go index 75a344868aa..35b04d336d6 100644 --- a/conn_id_manager.go +++ b/conn_id_manager.go @@ -1,10 +1,7 @@ package quic import ( - "crypto/rand" - "encoding/binary" "fmt" - mrand "math/rand" "github.com/lucas-clemente/quic-go/internal/protocol" "github.com/lucas-clemente/quic-go/internal/qerr" @@ -25,7 +22,7 @@ type connIDManager struct { // protocol.PacketsPerConnectionID packets. The actual value is randomized // hide the packet loss rate from on-path observers. packetsSinceLastChange uint64 - rand *mrand.Rand + rand utils.Rand packetsPerConnectionID uint64 addStatelessResetToken func(protocol.StatelessResetToken) @@ -39,15 +36,11 @@ func newConnIDManager( removeStatelessResetToken func(protocol.StatelessResetToken), queueControlFrame func(wire.Frame), ) *connIDManager { - b := make([]byte, 8) - _, _ = rand.Read(b) // ignore the error here. Nothing bad will happen if the seed is not perfectly random. - seed := int64(binary.BigEndian.Uint64(b)) return &connIDManager{ activeConnectionID: initialDestConnID, addStatelessResetToken: addStatelessResetToken, removeStatelessResetToken: removeStatelessResetToken, queueControlFrame: queueControlFrame, - rand: mrand.New(mrand.NewSource(seed)), } } @@ -155,7 +148,7 @@ func (h *connIDManager) updateConnectionID() { h.activeConnectionID = front.ConnectionID h.activeStatelessResetToken = &front.StatelessResetToken h.packetsSinceLastChange = 0 - h.packetsPerConnectionID = protocol.PacketsPerConnectionID/2 + uint64(h.rand.Int63n(protocol.PacketsPerConnectionID)) + h.packetsPerConnectionID = protocol.PacketsPerConnectionID/2 + uint64(h.rand.Int31n(protocol.PacketsPerConnectionID)) h.addStatelessResetToken(*h.activeStatelessResetToken) } From 03fe6367112572af6fd410b974d7e0f3388c61f0 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Wed, 17 Feb 2021 11:41:08 +0800 Subject: [PATCH 3/3] reduce memory footprint of the connIDManager --- conn_id_manager.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/conn_id_manager.go b/conn_id_manager.go index 35b04d336d6..99462376ecf 100644 --- a/conn_id_manager.go +++ b/conn_id_manager.go @@ -21,9 +21,9 @@ type connIDManager struct { // We change the connection ID after sending on average // protocol.PacketsPerConnectionID packets. The actual value is randomized // hide the packet loss rate from on-path observers. - packetsSinceLastChange uint64 rand utils.Rand - packetsPerConnectionID uint64 + packetsSinceLastChange uint32 + packetsPerConnectionID uint32 addStatelessResetToken func(protocol.StatelessResetToken) removeStatelessResetToken func(protocol.StatelessResetToken) @@ -148,7 +148,7 @@ func (h *connIDManager) updateConnectionID() { h.activeConnectionID = front.ConnectionID h.activeStatelessResetToken = &front.StatelessResetToken h.packetsSinceLastChange = 0 - h.packetsPerConnectionID = protocol.PacketsPerConnectionID/2 + uint64(h.rand.Int31n(protocol.PacketsPerConnectionID)) + h.packetsPerConnectionID = protocol.PacketsPerConnectionID/2 + uint32(h.rand.Int31n(protocol.PacketsPerConnectionID)) h.addStatelessResetToken(*h.activeStatelessResetToken) }