forked from quic-go/quic-go
retransmit path probe packets (#5005)
New PATH_CHALLENGES are sent for as long as the call to Path.Probe is active. The first retransmission is sent after 200ms, with an exponential backoff afterwards.
This commit is contained in:
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPathManagerOutgoing(t *testing.T) {
|
||||
func TestPathManagerOutgoingPathProbing(t *testing.T) {
|
||||
connIDs := []protocol.ConnectionID{
|
||||
protocol.ParseConnectionID([]byte{1, 2, 3, 4, 5, 6, 7, 8}),
|
||||
protocol.ParseConnectionID([]byte{2, 3, 4, 5, 6, 7, 8, 9}),
|
||||
@@ -28,7 +28,7 @@ func TestPathManagerOutgoing(t *testing.T) {
|
||||
|
||||
tr1 := &Transport{}
|
||||
var enabled bool
|
||||
p := pm.NewPath(tr1, func() { enabled = true })
|
||||
p := pm.NewPath(tr1, time.Second, func() { enabled = true })
|
||||
require.ErrorIs(t, p.Switch(), ErrPathNotValidated)
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
@@ -97,3 +97,105 @@ func TestPathManagerOutgoing(t *testing.T) {
|
||||
require.True(t, ok)
|
||||
require.Equal(t, tr1, switchToTransport)
|
||||
}
|
||||
|
||||
func TestPathManagerOutgoingRetransmissions(t *testing.T) {
|
||||
connIDs := []protocol.ConnectionID{
|
||||
protocol.ParseConnectionID([]byte{1, 2, 3, 4, 5, 6, 7, 8}),
|
||||
protocol.ParseConnectionID([]byte{2, 3, 4, 5, 6, 7, 8, 9}),
|
||||
}
|
||||
var retiredConnIDs []protocol.ConnectionID
|
||||
scheduledSending := make(chan struct{}, 20)
|
||||
pm := newPathManagerOutgoing(
|
||||
func(id pathID) (protocol.ConnectionID, bool) { return connIDs[id], true },
|
||||
func(id pathID) { retiredConnIDs = append(retiredConnIDs, connIDs[id]) },
|
||||
func() { scheduledSending <- struct{}{} },
|
||||
)
|
||||
|
||||
_, _, _, ok := pm.NextPathToProbe()
|
||||
require.False(t, ok)
|
||||
|
||||
tr1 := &Transport{}
|
||||
initialRTT := scaleDuration(2 * time.Millisecond)
|
||||
p := pm.NewPath(tr1, initialRTT, func() {})
|
||||
|
||||
pathChallengeChan := make(chan [8]byte)
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-scheduledSending:
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
_, f, _, ok := pm.NextPathToProbe()
|
||||
if !ok {
|
||||
// should never happen
|
||||
pathChallengeChan <- [8]byte{}
|
||||
continue
|
||||
}
|
||||
pathChallengeChan <- f.Frame.(*wire.PathChallengeFrame).Data
|
||||
}
|
||||
}()
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
go func() { errChan <- p.Probe(context.Background()) }()
|
||||
|
||||
start := time.Now()
|
||||
var pathChallenges [][8]byte
|
||||
for range 4 {
|
||||
select {
|
||||
case err := <-errChan:
|
||||
require.NoError(t, err)
|
||||
case pc := <-pathChallengeChan:
|
||||
pathChallenges = append(pathChallenges, pc)
|
||||
case <-time.After(scaleDuration(time.Second)):
|
||||
t.Fatal("timeout")
|
||||
}
|
||||
}
|
||||
took := time.Since(start)
|
||||
|
||||
require.NotContains(t, pathChallenges, [8]byte{})
|
||||
require.NotEqual(t, pathChallenges[0], pathChallenges[1])
|
||||
require.NotEqual(t, pathChallenges[0], pathChallenges[2])
|
||||
require.NotEqual(t, pathChallenges[0], pathChallenges[3])
|
||||
require.NotEqual(t, pathChallenges[1], pathChallenges[2])
|
||||
require.NotEqual(t, pathChallenges[2], pathChallenges[3])
|
||||
|
||||
require.Greater(t, took, initialRTT*(1+2+4+8))
|
||||
require.Less(t, took, initialRTT*(1+2+4+8)*3/2)
|
||||
|
||||
// receiving a PATH_RESPONSE for any of the PATH_CHALLENGES completes path validation
|
||||
pm.HandlePathResponseFrame(&wire.PathResponseFrame{Data: pathChallenges[2]})
|
||||
|
||||
select {
|
||||
case err := <-errChan:
|
||||
require.NoError(t, err)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timeout")
|
||||
}
|
||||
|
||||
// It is valid to probe again
|
||||
pathChallenges = pathChallenges[:0]
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() { errChan <- p.Probe(ctx) }()
|
||||
|
||||
for range 2 {
|
||||
select {
|
||||
case err := <-errChan:
|
||||
require.NoError(t, err)
|
||||
case pc := <-pathChallengeChan:
|
||||
pathChallenges = append(pathChallenges, pc)
|
||||
case <-time.After(scaleDuration(time.Second)):
|
||||
t.Fatal("timeout")
|
||||
}
|
||||
}
|
||||
// this time, don't receive a PATH_RESPONSE
|
||||
cancel()
|
||||
select {
|
||||
case err := <-errChan:
|
||||
require.ErrorIs(t, err, context.Canceled)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timeout")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user