mirror of
https://github.com/refraction-networking/uquic.git
synced 2025-04-04 20:57:36 +03:00
migrate the MTU discoverer tests away from Ginkgo (#4907)
This commit is contained in:
parent
bea70c6489
commit
302308335c
2 changed files with 179 additions and 173 deletions
|
@ -22,7 +22,7 @@ type mtuDiscoverer interface {
|
||||||
const (
|
const (
|
||||||
// At some point, we have to stop searching for a higher MTU.
|
// At some point, we have to stop searching for a higher MTU.
|
||||||
// We're happy to send a packet that's 10 bytes smaller than the actual MTU.
|
// We're happy to send a packet that's 10 bytes smaller than the actual MTU.
|
||||||
maxMTUDiff = 20
|
maxMTUDiff protocol.ByteCount = 20
|
||||||
// send a probe packet every mtuProbeDelay RTTs
|
// send a probe packet every mtuProbeDelay RTTs
|
||||||
mtuProbeDelay = 5
|
mtuProbeDelay = 5
|
||||||
// Once maxLostMTUProbes MTU probe packets larger than a certain size are lost,
|
// Once maxLostMTUProbes MTU probe packets larger than a certain size are lost,
|
||||||
|
|
|
@ -2,200 +2,206 @@ package quic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand/v2"
|
||||||
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/exp/rand"
|
|
||||||
|
|
||||||
"github.com/quic-go/quic-go/internal/protocol"
|
"github.com/quic-go/quic-go/internal/protocol"
|
||||||
"github.com/quic-go/quic-go/internal/utils"
|
"github.com/quic-go/quic-go/internal/utils"
|
||||||
"github.com/quic-go/quic-go/logging"
|
"github.com/quic-go/quic-go/logging"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
"github.com/stretchr/testify/require"
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("MTU Discoverer", func() {
|
func TestMTUDiscovererTiming(t *testing.T) {
|
||||||
const (
|
const rtt = 100 * time.Millisecond
|
||||||
rtt = 100 * time.Millisecond
|
var rttStats utils.RTTStats
|
||||||
startMTU protocol.ByteCount = 1000
|
rttStats.UpdateRTT(rtt, 0)
|
||||||
maxMTU protocol.ByteCount = 2000
|
d := newMTUDiscoverer(&rttStats, 1000, 2000, func(s protocol.ByteCount) {}, nil)
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
now := time.Now()
|
||||||
d *mtuFinder
|
require.False(t, d.ShouldSendProbe(now))
|
||||||
rttStats *utils.RTTStats
|
d.Start(now)
|
||||||
now time.Time
|
require.False(t, d.ShouldSendProbe(now))
|
||||||
discoveredMTU protocol.ByteCount
|
require.False(t, d.ShouldSendProbe(now.Add(rtt*9/2)))
|
||||||
)
|
now = now.Add(5 * rtt)
|
||||||
r := rand.New(rand.NewSource(uint64(GinkgoRandomSeed())))
|
require.True(t, d.ShouldSendProbe(now))
|
||||||
|
|
||||||
BeforeEach(func() {
|
// only a single outstanding probe packet is permitted
|
||||||
rttStats = &utils.RTTStats{}
|
ping, _ := d.GetPing(now)
|
||||||
rttStats.SetInitialRTT(rtt)
|
require.False(t, d.ShouldSendProbe(now))
|
||||||
Expect(rttStats.SmoothedRTT()).To(Equal(rtt))
|
now = now.Add(5 * rtt)
|
||||||
d = newMTUDiscoverer(
|
require.False(t, d.ShouldSendProbe(now))
|
||||||
rttStats,
|
ping.Handler.OnLost(ping.Frame)
|
||||||
startMTU,
|
require.True(t, d.ShouldSendProbe(now))
|
||||||
maxMTU,
|
}
|
||||||
func(s protocol.ByteCount) { discoveredMTU = s },
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
d.Start(time.Now())
|
|
||||||
now = time.Now()
|
|
||||||
})
|
|
||||||
|
|
||||||
It("only allows a probe 5 RTTs after the handshake completes", func() {
|
func TestMTUDiscovererAckAndLoss(t *testing.T) {
|
||||||
Expect(d.ShouldSendProbe(now)).To(BeFalse())
|
var mtu protocol.ByteCount
|
||||||
Expect(d.ShouldSendProbe(now.Add(rtt * 9 / 2))).To(BeFalse())
|
d := newMTUDiscoverer(&utils.RTTStats{}, 1000, 2000, func(s protocol.ByteCount) { mtu = s }, nil)
|
||||||
Expect(d.ShouldSendProbe(now.Add(rtt * 5))).To(BeTrue())
|
// we use an RTT of 0 here, so we don't have to advance the timer on every step
|
||||||
})
|
now := time.Now()
|
||||||
|
ping, size := d.GetPing(now)
|
||||||
|
require.Equal(t, protocol.ByteCount(1500), size)
|
||||||
|
// the MTU is reduced if the frame is lost
|
||||||
|
ping.Handler.OnLost(ping.Frame)
|
||||||
|
require.Zero(t, mtu) // no change to the MTU yet
|
||||||
|
|
||||||
It("doesn't allow a probe if another probe is still in flight", func() {
|
require.True(t, d.ShouldSendProbe(now))
|
||||||
ping, _ := d.GetPing(time.Now())
|
ping, size = d.GetPing(now)
|
||||||
Expect(d.ShouldSendProbe(now.Add(10 * rtt))).To(BeFalse())
|
require.Equal(t, protocol.ByteCount(1250), size)
|
||||||
ping.Handler.OnLost(ping.Frame)
|
ping.Handler.OnAcked(ping.Frame)
|
||||||
Expect(d.ShouldSendProbe(now.Add(10 * rtt))).To(BeTrue())
|
require.Equal(t, protocol.ByteCount(1250), mtu) // the MTU is increased
|
||||||
})
|
|
||||||
|
|
||||||
It("tries a lower size when a probe is lost", func() {
|
// Even though the 1500 byte MTU probe packet was lost, we try again with a higher MTU.
|
||||||
ping, size := d.GetPing(time.Now())
|
// This protects against regular (non-MTU-related) packet loss.
|
||||||
Expect(size).To(Equal(protocol.ByteCount(1500)))
|
require.True(t, d.ShouldSendProbe(now))
|
||||||
ping.Handler.OnLost(ping.Frame)
|
ping, size = d.GetPing(now)
|
||||||
_, size = d.GetPing(time.Now())
|
require.Greater(t, size, protocol.ByteCount(1500))
|
||||||
Expect(size).To(Equal(protocol.ByteCount(1250)))
|
ping.Handler.OnAcked(ping.Frame)
|
||||||
})
|
require.Equal(t, size, mtu)
|
||||||
|
|
||||||
It("tries a higher size and calls the callback when a probe is acknowledged", func() {
|
// We continue probing until the MTU is close to the maximum.
|
||||||
ping, size := d.GetPing(time.Now())
|
var steps int
|
||||||
Expect(size).To(Equal(protocol.ByteCount(1500)))
|
oldSize := size
|
||||||
|
for d.ShouldSendProbe(now) {
|
||||||
|
ping, size = d.GetPing(now)
|
||||||
|
require.Greater(t, size, oldSize)
|
||||||
|
oldSize = size
|
||||||
ping.Handler.OnAcked(ping.Frame)
|
ping.Handler.OnAcked(ping.Frame)
|
||||||
Expect(discoveredMTU).To(Equal(protocol.ByteCount(1500)))
|
steps++
|
||||||
_, size = d.GetPing(time.Now())
|
require.Less(t, steps, 10)
|
||||||
Expect(size).To(Equal(protocol.ByteCount(1750)))
|
}
|
||||||
})
|
require.Less(t, 2000-maxMTUDiff, size)
|
||||||
|
}
|
||||||
|
|
||||||
It("stops discovery after getting close enough to the MTU", func() {
|
func TestMTUDiscovererMTUDiscovery(t *testing.T) {
|
||||||
var sizes []protocol.ByteCount
|
for i := range 5 {
|
||||||
t := now.Add(5 * rtt)
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||||
for d.ShouldSendProbe(t) {
|
testMTUDiscovererMTUDiscovery(t)
|
||||||
ping, size := d.GetPing(time.Now())
|
})
|
||||||
fmt.Println("sending", size)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMTUDiscovererMTUDiscovery(t *testing.T) {
|
||||||
|
const rtt = 100 * time.Millisecond
|
||||||
|
const startMTU protocol.ByteCount = 1000
|
||||||
|
|
||||||
|
var rttStats utils.RTTStats
|
||||||
|
rttStats.UpdateRTT(rtt, 0)
|
||||||
|
|
||||||
|
maxMTU := protocol.ByteCount(rand.IntN(int(3000-startMTU))) + startMTU + 1
|
||||||
|
currentMTU := startMTU
|
||||||
|
var tracedMTU protocol.ByteCount
|
||||||
|
var tracerDone bool
|
||||||
|
d := newMTUDiscoverer(
|
||||||
|
&rttStats,
|
||||||
|
startMTU, maxMTU,
|
||||||
|
func(s protocol.ByteCount) { currentMTU = s },
|
||||||
|
&logging.ConnectionTracer{
|
||||||
|
UpdatedMTU: func(mtu logging.ByteCount, done bool) {
|
||||||
|
tracedMTU = mtu
|
||||||
|
tracerDone = done
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
now := time.Now()
|
||||||
|
d.Start(now)
|
||||||
|
realMTU := protocol.ByteCount(rand.IntN(int(maxMTU-startMTU))) + startMTU
|
||||||
|
t.Logf("MTU: %d, max: %d", realMTU, maxMTU)
|
||||||
|
now = now.Add(mtuProbeDelay * rtt)
|
||||||
|
var probes []protocol.ByteCount
|
||||||
|
for d.ShouldSendProbe(now) {
|
||||||
|
require.Less(t, len(probes), 25, fmt.Sprintf("too many iterations: %v", probes))
|
||||||
|
ping, size := d.GetPing(now)
|
||||||
|
probes = append(probes, size)
|
||||||
|
if size <= realMTU {
|
||||||
ping.Handler.OnAcked(ping.Frame)
|
ping.Handler.OnAcked(ping.Frame)
|
||||||
sizes = append(sizes, size)
|
} else {
|
||||||
t = t.Add(5 * rtt)
|
ping.Handler.OnLost(ping.Frame)
|
||||||
}
|
}
|
||||||
Expect(sizes).To(Equal([]protocol.ByteCount{1500, 1750, 1875, 1937, 1968, 1984}))
|
now = now.Add(mtuProbeDelay * rtt)
|
||||||
Expect(d.ShouldSendProbe(t.Add(10 * rtt))).To(BeFalse())
|
}
|
||||||
})
|
diff := realMTU - currentMTU
|
||||||
|
require.GreaterOrEqual(t, diff, protocol.ByteCount(0))
|
||||||
|
if maxMTU > currentMTU+maxMTU {
|
||||||
|
require.Equal(t, currentMTU, tracedMTU)
|
||||||
|
require.True(t, tracerDone)
|
||||||
|
}
|
||||||
|
t.Logf("MTU discovered: %d (diff: %d)", currentMTU, diff)
|
||||||
|
t.Logf("probes sent (%d): %v", len(probes), probes)
|
||||||
|
require.LessOrEqual(t, diff, maxMTUDiff)
|
||||||
|
}
|
||||||
|
|
||||||
It("doesn't do discovery before being started", func() {
|
func TestMTUDiscovererWithRandomLoss(t *testing.T) {
|
||||||
d := newMTUDiscoverer(rttStats, startMTU, protocol.MaxByteCount, func(s protocol.ByteCount) {}, nil)
|
for i := range 5 {
|
||||||
for i := 0; i < 5; i++ {
|
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||||
Expect(d.ShouldSendProbe(time.Now())).To(BeFalse())
|
testMTUDiscovererWithRandomLoss(t)
|
||||||
}
|
})
|
||||||
})
|
}
|
||||||
|
}
|
||||||
It("finds the MTU", MustPassRepeatedly(300), func() {
|
|
||||||
maxMTU := protocol.ByteCount(r.Intn(int(3000-startMTU))) + startMTU + 1
|
|
||||||
currentMTU := startMTU
|
|
||||||
var tracedMTU protocol.ByteCount
|
|
||||||
var tracerDone bool
|
|
||||||
d := newMTUDiscoverer(
|
|
||||||
rttStats,
|
|
||||||
startMTU,
|
|
||||||
maxMTU,
|
|
||||||
func(s protocol.ByteCount) { currentMTU = s },
|
|
||||||
&logging.ConnectionTracer{
|
|
||||||
UpdatedMTU: func(mtu logging.ByteCount, done bool) {
|
|
||||||
tracedMTU = mtu
|
|
||||||
tracerDone = done
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
d.Start(time.Now())
|
|
||||||
now := time.Now()
|
|
||||||
realMTU := protocol.ByteCount(r.Intn(int(maxMTU-startMTU))) + startMTU
|
|
||||||
fmt.Fprintf(GinkgoWriter, "MTU: %d, max: %d\n", realMTU, maxMTU)
|
|
||||||
t := now.Add(mtuProbeDelay * rtt)
|
|
||||||
var probes []protocol.ByteCount
|
|
||||||
for d.ShouldSendProbe(t) {
|
|
||||||
if len(probes) > 24 {
|
|
||||||
Fail(fmt.Sprintf("too many iterations: %v", probes))
|
|
||||||
}
|
|
||||||
ping, size := d.GetPing(time.Now())
|
|
||||||
probes = append(probes, size)
|
|
||||||
if size <= realMTU {
|
|
||||||
ping.Handler.OnAcked(ping.Frame)
|
|
||||||
} else {
|
|
||||||
ping.Handler.OnLost(ping.Frame)
|
|
||||||
}
|
|
||||||
t = t.Add(mtuProbeDelay * rtt)
|
|
||||||
}
|
|
||||||
diff := realMTU - currentMTU
|
|
||||||
Expect(diff).To(BeNumerically(">=", 0))
|
|
||||||
if maxMTU > currentMTU+maxMTU {
|
|
||||||
Expect(tracedMTU).To(Equal(currentMTU))
|
|
||||||
Expect(tracerDone).To(BeTrue())
|
|
||||||
}
|
|
||||||
fmt.Fprintf(GinkgoWriter, "MTU discovered: %d (diff: %d)\n", currentMTU, diff)
|
|
||||||
fmt.Fprintf(GinkgoWriter, "probes sent (%d): %v\n", len(probes), probes)
|
|
||||||
Expect(diff).To(BeNumerically("<=", maxMTUDiff))
|
|
||||||
})
|
|
||||||
|
|
||||||
|
func testMTUDiscovererWithRandomLoss(t *testing.T) {
|
||||||
|
const rtt = 100 * time.Millisecond
|
||||||
|
const startMTU protocol.ByteCount = 1000
|
||||||
const maxRandomLoss = maxLostMTUProbes - 1
|
const maxRandomLoss = maxLostMTUProbes - 1
|
||||||
It(fmt.Sprintf("finds the MTU, with up to %d packets lost", maxRandomLoss), MustPassRepeatedly(500), func() {
|
|
||||||
maxMTU := protocol.ByteCount(r.Intn(int(3000-startMTU))) + startMTU + 1
|
rttStats := &utils.RTTStats{}
|
||||||
currentMTU := startMTU
|
rttStats.SetInitialRTT(rtt)
|
||||||
var tracedMTU protocol.ByteCount
|
require.Equal(t, rtt, rttStats.SmoothedRTT())
|
||||||
var tracerDone bool
|
|
||||||
d := newMTUDiscoverer(
|
maxMTU := protocol.ByteCount(rand.IntN(int(3000-startMTU))) + startMTU + 1
|
||||||
rttStats,
|
currentMTU := startMTU
|
||||||
startMTU,
|
var tracedMTU protocol.ByteCount
|
||||||
maxMTU,
|
var tracerDone bool
|
||||||
func(s protocol.ByteCount) { currentMTU = s },
|
|
||||||
&logging.ConnectionTracer{
|
d := newMTUDiscoverer(
|
||||||
UpdatedMTU: func(mtu logging.ByteCount, done bool) {
|
rttStats,
|
||||||
tracedMTU = mtu
|
startMTU,
|
||||||
tracerDone = done
|
maxMTU,
|
||||||
},
|
func(s protocol.ByteCount) { currentMTU = s },
|
||||||
|
&logging.ConnectionTracer{
|
||||||
|
UpdatedMTU: func(mtu logging.ByteCount, done bool) {
|
||||||
|
tracedMTU = mtu
|
||||||
|
tracerDone = done
|
||||||
},
|
},
|
||||||
)
|
},
|
||||||
d.Start(time.Now())
|
)
|
||||||
now := time.Now()
|
d.Start(time.Now())
|
||||||
realMTU := protocol.ByteCount(r.Intn(int(maxMTU-startMTU))) + startMTU
|
now := time.Now()
|
||||||
fmt.Fprintf(GinkgoWriter, "MTU: %d, max: %d\n", realMTU, maxMTU)
|
realMTU := protocol.ByteCount(rand.IntN(int(maxMTU-startMTU))) + startMTU
|
||||||
t := now.Add(mtuProbeDelay * rtt)
|
t.Logf("MTU: %d, max: %d", realMTU, maxMTU)
|
||||||
var probes, randomLosses []protocol.ByteCount
|
now = now.Add(mtuProbeDelay * rtt)
|
||||||
for d.ShouldSendProbe(t) {
|
var probes, randomLosses []protocol.ByteCount
|
||||||
if len(probes) > 32 {
|
|
||||||
Fail(fmt.Sprintf("too many iterations: %v", probes))
|
for d.ShouldSendProbe(now) {
|
||||||
|
require.Less(t, len(probes), 32, fmt.Sprintf("too many iterations: %v", probes))
|
||||||
|
ping, size := d.GetPing(now)
|
||||||
|
probes = append(probes, size)
|
||||||
|
packetFits := size <= realMTU
|
||||||
|
var acked bool
|
||||||
|
if packetFits {
|
||||||
|
randomLoss := rand.IntN(maxLostMTUProbes) == 0 && len(randomLosses) < maxRandomLoss
|
||||||
|
if randomLoss {
|
||||||
|
randomLosses = append(randomLosses, size)
|
||||||
|
} else {
|
||||||
|
ping.Handler.OnAcked(ping.Frame)
|
||||||
|
acked = true
|
||||||
}
|
}
|
||||||
ping, size := d.GetPing(time.Now())
|
|
||||||
probes = append(probes, size)
|
|
||||||
packetFits := size <= realMTU
|
|
||||||
var acked bool
|
|
||||||
if packetFits {
|
|
||||||
randomLoss := r.Intn(maxLostMTUProbes) == 0 && len(randomLosses) < maxRandomLoss
|
|
||||||
if randomLoss {
|
|
||||||
randomLosses = append(randomLosses, size)
|
|
||||||
} else {
|
|
||||||
ping.Handler.OnAcked(ping.Frame)
|
|
||||||
acked = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !acked {
|
|
||||||
ping.Handler.OnLost(ping.Frame)
|
|
||||||
}
|
|
||||||
t = t.Add(mtuProbeDelay * rtt)
|
|
||||||
}
|
}
|
||||||
diff := realMTU - currentMTU
|
if !acked {
|
||||||
Expect(diff).To(BeNumerically(">=", 0))
|
ping.Handler.OnLost(ping.Frame)
|
||||||
if maxMTU > currentMTU+maxMTU {
|
|
||||||
Expect(tracedMTU).To(Equal(currentMTU))
|
|
||||||
Expect(tracerDone).To(BeTrue())
|
|
||||||
}
|
}
|
||||||
fmt.Fprintf(GinkgoWriter, "MTU discovered with random losses %v: %d (diff: %d)\n", randomLosses, currentMTU, diff)
|
now = now.Add(mtuProbeDelay * rtt)
|
||||||
fmt.Fprintf(GinkgoWriter, "probes sent (%d): %v\n", len(probes), probes)
|
}
|
||||||
Expect(diff).To(BeNumerically("<=", maxMTUDiff))
|
|
||||||
})
|
diff := realMTU - currentMTU
|
||||||
})
|
require.GreaterOrEqual(t, diff, protocol.ByteCount(0))
|
||||||
|
if maxMTU > currentMTU+maxMTU {
|
||||||
|
require.Equal(t, currentMTU, tracedMTU)
|
||||||
|
require.True(t, tracerDone)
|
||||||
|
}
|
||||||
|
t.Logf("MTU discovered with random losses %v: %d (diff: %d)", randomLosses, currentMTU, diff)
|
||||||
|
t.Logf("probes sent (%d): %v", len(probes), probes)
|
||||||
|
require.LessOrEqual(t, diff, maxMTUDiff)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue