Merge pull request #1692 from lucas-clemente/unify-tlp-and-rto

unify TLP and RTO
This commit is contained in:
Marten Seemann
2019-01-03 10:07:00 +07:00
committed by GitHub
6 changed files with 82 additions and 245 deletions

View File

@@ -12,10 +12,8 @@ const (
SendAck
// SendRetransmission means that retransmissions should be sent
SendRetransmission
// SendRTO means that an RTO probe packet should be sent
SendRTO
// SendTLP means that a TLP probe packet should be sent
SendTLP
// SendPTO means that a probe packet should be sent
SendPTO
// SendAny means that any packet should be sent
SendAny
)
@@ -28,10 +26,8 @@ func (s SendMode) String() string {
return "ack"
case SendRetransmission:
return "retransmission"
case SendRTO:
return "rto"
case SendTLP:
return "tlp"
case SendPTO:
return "pto"
case SendAny:
return "any"
default:

View File

@@ -10,8 +10,7 @@ var _ = Describe("Send Mode", func() {
Expect(SendNone.String()).To(Equal("none"))
Expect(SendAny.String()).To(Equal("any"))
Expect(SendAck.String()).To(Equal("ack"))
Expect(SendRTO.String()).To(Equal("rto"))
Expect(SendTLP.String()).To(Equal("tlp"))
Expect(SendPTO.String()).To(Equal("pto"))
Expect(SendRetransmission.String()).To(Equal("retransmission"))
Expect(SendMode(123).String()).To(Equal("invalid send mode: 123"))
})

View File

@@ -17,16 +17,8 @@ const (
// Maximum reordering in time space before time based loss detection considers a packet lost.
// In fraction of an RTT.
timeReorderingFraction = 1.0 / 8
// defaultRTOTimeout is the RTO time on new connections
defaultRTOTimeout = 500 * time.Millisecond
// Minimum time in the future a tail loss probe alarm may be set for.
minTPLTimeout = 10 * time.Millisecond
// Maximum number of tail loss probes before an RTO fires.
maxTLPs = 2
// Minimum time in the future an RTO alarm may be set for.
minRTOTimeout = 200 * time.Millisecond
// maxRTOTimeout is the maximum RTO time
maxRTOTimeout = 60 * time.Second
// Timer granularity. The timer will not be set to a value smaller than granularity.
granularity = time.Millisecond
)
type sentPacketHandler struct {
@@ -44,7 +36,6 @@ type sentPacketHandler struct {
// example: we send an ACK for packets 90-100 with packet number 20
// once we receive an ACK from the peer for packet 20, the lowestPacketNotConfirmedAcked is 101
lowestPacketNotConfirmedAcked protocol.PacketNumber
largestSentBeforeRTO protocol.PacketNumber
packetHistory *sentPacketHistory
@@ -56,17 +47,13 @@ type sentPacketHandler struct {
rttStats *congestion.RTTStats
handshakeComplete bool
// The number of times the crypto packets have been retransmitted without receiving an ack.
cryptoCount uint32
// The number of times a TLP has been sent without receiving an ack.
tlpCount uint32
allowTLP bool
// The number of times an RTO has been sent without receiving an ack.
rtoCount uint32
// The number of RTO probe packets that should be sent.
numRTOs int
// The number of times a PTO has been sent without receiving an ack.
ptoCount uint32
// The number of PTO probe packets that should be sent.
numProbesToSend int
// The time at which the next packet will be considered lost based on early transmit or exceeding the reordering window in time.
lossTime time.Time
@@ -173,10 +160,9 @@ func (h *sentPacketHandler) sentPacketImpl(packet *Packet) bool /* isRetransmitt
packet.includedInBytesInFlight = true
h.bytesInFlight += packet.Length
packet.canBeRetransmitted = true
if h.numRTOs > 0 {
h.numRTOs--
if h.numProbesToSend > 0 {
h.numProbesToSend--
}
h.allowTLP = false
}
h.congestion.OnPacketSent(packet.SendTime, h.bytesInFlight, packet.PacketNumber, packet.Length, isRetransmittable)
@@ -210,6 +196,9 @@ func (h *sentPacketHandler) ReceivedAck(ackFrame *wire.AckFrame, withPacketNumbe
if err != nil {
return err
}
if len(ackedPackets) == 0 {
return nil
}
priorInFlight := h.bytesInFlight
for _, p := range ackedPackets {
@@ -235,6 +224,10 @@ func (h *sentPacketHandler) ReceivedAck(ackFrame *wire.AckFrame, withPacketNumbe
if err := h.detectLostPackets(rcvTime, priorInFlight); err != nil {
return err
}
h.ptoCount = 0
h.cryptoCount = 0
h.updateLossDetectionAlarm()
return nil
}
@@ -310,15 +303,8 @@ func (h *sentPacketHandler) updateLossDetectionAlarm() {
} else if !h.lossTime.IsZero() {
// Early retransmit timer or time loss detection.
h.alarm = h.lossTime
} else {
// RTO or TLP alarm
alarmDuration := h.computeRTOTimeout()
if h.tlpCount < maxTLPs {
tlpAlarm := h.computeTLPTimeout()
// if the RTO duration is shorter than the TLP duration, use the RTO duration
alarmDuration = utils.MinDuration(alarmDuration, tlpAlarm)
}
h.alarm = h.lastSentRetransmittablePacketTime.Add(alarmDuration)
} else { // PTO alarm
h.alarm = h.lastSentRetransmittablePacketTime.Add(h.computePTOTimeout())
}
}
@@ -346,6 +332,7 @@ func (h *sentPacketHandler) detectLostPackets(now time.Time, priorInFlight proto
}
return true, nil
})
if h.logger.Debug() && len(lostPackets) > 0 {
pns := make([]protocol.PacketNumber, len(lostPackets))
for i, p := range lostPackets {
@@ -399,21 +386,12 @@ func (h *sentPacketHandler) onVerifiedAlarm() error {
}
// Early retransmit or time loss detection
err = h.detectLostPackets(time.Now(), h.bytesInFlight)
} else if h.tlpCount < maxTLPs { // TLP
} else { // PTO
if h.logger.Debug() {
h.logger.Debugf("Loss detection alarm fired in TLP mode. TLP count: %d", h.tlpCount)
h.logger.Debugf("Loss detection alarm fired in PTO mode. PTO count: %d", h.ptoCount)
}
h.allowTLP = true
h.tlpCount++
} else { // RTO
if h.logger.Debug() {
h.logger.Debugf("Loss detection alarm fired in RTO mode. RTO count: %d", h.rtoCount)
}
if h.rtoCount == 0 {
h.largestSentBeforeRTO = h.lastSentPacketNumber
}
h.rtoCount++
h.numRTOs += 2
h.ptoCount++
h.numProbesToSend += 2
}
return err
}
@@ -454,15 +432,9 @@ func (h *sentPacketHandler) onPacketAcked(p *Packet, rcvTime time.Time) error {
if p.includedInBytesInFlight {
h.bytesInFlight -= p.Length
}
if h.rtoCount > 0 {
h.verifyRTO(p.PacketNumber)
}
if err := h.stopRetransmissionsFor(p); err != nil {
return err
}
h.rtoCount = 0
h.tlpCount = 0
h.cryptoCount = 0
return h.packetHistory.Remove(p.PacketNumber)
}
@@ -480,18 +452,6 @@ func (h *sentPacketHandler) stopRetransmissionsFor(p *Packet) error {
return nil
}
func (h *sentPacketHandler) verifyRTO(pn protocol.PacketNumber) {
if pn <= h.largestSentBeforeRTO {
h.logger.Debugf("Spurious RTO detected. Received an ACK for %#x (largest sent before RTO: %#x)", pn, h.largestSentBeforeRTO)
// Replace SRTT with latest_rtt and increase the variance to prevent
// a spurious RTO from happening again.
h.rttStats.ExpireSmoothedMetrics()
return
}
h.logger.Debugf("RTO verified. Received an ACK for %#x (largest sent before RTO: %#x", pn, h.largestSentBeforeRTO)
h.congestion.OnRetransmissionTimeout(true)
}
func (h *sentPacketHandler) DequeuePacketForRetransmission() *Packet {
if len(h.retransmissionQueue) == 0 {
return nil
@@ -539,11 +499,8 @@ func (h *sentPacketHandler) SendMode() SendMode {
}
return SendNone
}
if h.allowTLP {
return SendTLP
}
if h.numRTOs > 0 {
return SendRTO
if h.numProbesToSend > 0 {
return SendPTO
}
// Only send ACKs if we're congestion limited.
if cwnd := h.congestion.GetCongestionWindow(); h.bytesInFlight > cwnd {
@@ -570,9 +527,9 @@ func (h *sentPacketHandler) TimeUntilSend() time.Time {
}
func (h *sentPacketHandler) ShouldSendNumPackets() int {
if h.numRTOs > 0 {
if h.numProbesToSend > 0 {
// RTO probes should not be paced, but must be sent immediately.
return h.numRTOs
return h.numProbesToSend
}
delay := h.congestion.TimeUntilSend(h.bytesInFlight)
if delay == 0 || delay > protocol.MinPacingDelay {
@@ -610,27 +567,14 @@ func (h *sentPacketHandler) queuePacketForRetransmission(p *Packet) error {
}
func (h *sentPacketHandler) computeCryptoTimeout() time.Duration {
duration := utils.MaxDuration(2*h.rttStats.SmoothedOrInitialRTT(), minTPLTimeout)
duration := utils.MaxDuration(2*h.rttStats.SmoothedOrInitialRTT(), granularity)
// exponential backoff
// There's an implicit limit to this set by the crypto timeout.
return duration << h.cryptoCount
}
func (h *sentPacketHandler) computeTLPTimeout() time.Duration {
func (h *sentPacketHandler) computePTOTimeout() time.Duration {
// TODO(#1236): include the max_ack_delay
return utils.MaxDuration(h.rttStats.SmoothedOrInitialRTT()*3/2, minTPLTimeout)
}
func (h *sentPacketHandler) computeRTOTimeout() time.Duration {
var rto time.Duration
rtt := h.rttStats.SmoothedRTT()
if rtt == 0 {
rto = defaultRTOTimeout
} else {
rto = rtt + 4*h.rttStats.MeanDeviation()
}
rto = utils.MaxDuration(rto, minRTOTimeout)
// Exponential backoff
rto <<= h.rtoCount
return utils.MinDuration(rto, maxRTOTimeout)
duration := utils.MaxDuration(h.rttStats.SmoothedOrInitialRTT()+4*h.rttStats.MeanDeviation(), granularity)
return duration << h.ptoCount
}

View File

@@ -468,67 +468,6 @@ var _ = Describe("SentPacketHandler", func() {
Expect(err).NotTo(HaveOccurred())
})
It("doesn't call OnPacketLost and OnRetransmissionTimeout when queuing RTOs", func() {
for i := protocol.PacketNumber(1); i < 3; i++ {
cong.EXPECT().OnPacketSent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
cong.EXPECT().TimeUntilSend(gomock.Any())
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: i}))
}
handler.OnAlarm() // TLP
handler.OnAlarm() // TLP
handler.OnAlarm() // RTO
})
It("declares all lower packets lost and call OnRetransmissionTimeout when verifying an RTO", func() {
cong.EXPECT().OnPacketSent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(5)
cong.EXPECT().TimeUntilSend(gomock.Any()).Times(5)
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 1, SendTime: time.Now().Add(-time.Hour)}))
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 2, SendTime: time.Now().Add(-time.Hour)}))
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 3, SendTime: time.Now().Add(-time.Hour)}))
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 4, SendTime: time.Now().Add(-time.Hour)}))
handler.OnAlarm() // TLP
handler.OnAlarm() // TLP
handler.OnAlarm() // RTO
// send one probe packet and receive an ACK for it
rcvTime := time.Now()
gomock.InOrder(
cong.EXPECT().MaybeExitSlowStart(),
cong.EXPECT().OnRetransmissionTimeout(true),
cong.EXPECT().OnPacketAcked(protocol.PacketNumber(5), protocol.ByteCount(1), protocol.ByteCount(5), rcvTime),
cong.EXPECT().OnPacketLost(protocol.PacketNumber(1), protocol.ByteCount(1), protocol.ByteCount(5)),
cong.EXPECT().OnPacketLost(protocol.PacketNumber(2), protocol.ByteCount(1), protocol.ByteCount(5)),
cong.EXPECT().OnPacketLost(protocol.PacketNumber(3), protocol.ByteCount(1), protocol.ByteCount(5)),
cong.EXPECT().OnPacketLost(protocol.PacketNumber(4), protocol.ByteCount(1), protocol.ByteCount(5)),
)
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 5}))
ack := &wire.AckFrame{AckRanges: []wire.AckRange{{Smallest: 5, Largest: 5}}}
err := handler.ReceivedAck(ack, 1, protocol.Encryption1RTT, rcvTime)
Expect(err).ToNot(HaveOccurred())
})
It("doesn't call OnRetransmissionTimeout when a spurious RTO occurs", func() {
cong.EXPECT().OnPacketSent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(3)
cong.EXPECT().TimeUntilSend(gomock.Any()).Times(3)
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 1, SendTime: time.Now().Add(-time.Hour)}))
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 2, SendTime: time.Now()}))
handler.OnAlarm() // TLP
handler.OnAlarm() // TLP
handler.OnAlarm() // RTO
// send one probe packet
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 3}))
// receive an ACK for a packet send *before* the probe packet
// don't EXPECT any call to OnRetransmissionTimeout
gomock.InOrder(
cong.EXPECT().MaybeExitSlowStart(),
cong.EXPECT().OnPacketAcked(protocol.PacketNumber(2), protocol.ByteCount(1), protocol.ByteCount(3), gomock.Any()),
cong.EXPECT().OnPacketLost(protocol.PacketNumber(1), protocol.ByteCount(1), protocol.ByteCount(3)),
)
ack := &wire.AckFrame{AckRanges: []wire.AckRange{{Smallest: 2, Largest: 2}}}
err := handler.ReceivedAck(ack, 1, protocol.Encryption1RTT, time.Now())
Expect(err).ToNot(HaveOccurred())
})
It("doesn't call OnPacketAcked when a retransmitted packet is acked", func() {
cong.EXPECT().OnPacketSent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(2)
cong.EXPECT().TimeUntilSend(gomock.Any()).Times(2)
@@ -621,9 +560,9 @@ var _ = Describe("SentPacketHandler", func() {
It("allows RTOs, even when congestion limited", func() {
// note that we don't EXPECT a call to GetCongestionWindow
// that means retransmissions are sent without considering the congestion window
handler.numRTOs = 1
handler.numProbesToSend = 1
handler.retransmissionQueue = []*Packet{{PacketNumber: 3}}
Expect(handler.SendMode()).To(Equal(SendRTO))
Expect(handler.SendMode()).To(Equal(SendPTO))
})
It("gets the pacing delay", func() {
@@ -636,7 +575,7 @@ var _ = Describe("SentPacketHandler", func() {
})
It("allows sending of all RTO probe packets", func() {
handler.numRTOs = 5
handler.numProbesToSend = 5
Expect(handler.ShouldSendNumPackets()).To(Equal(5))
})
@@ -672,81 +611,55 @@ var _ = Describe("SentPacketHandler", func() {
Expect(handler.SendMode()).To(Equal(SendAny))
})
Context("TLPs", func() {
Context("probe packets", func() {
It("uses the RTT from RTT stats", func() {
rtt := 2 * time.Second
updateRTT(rtt)
Expect(handler.computeTLPTimeout()).To(Equal(rtt * 3 / 2))
Expect(handler.rttStats.SmoothedOrInitialRTT()).To(Equal(2 * time.Second))
Expect(handler.rttStats.MeanDeviation()).To(Equal(time.Second))
Expect(handler.computePTOTimeout()).To(Equal(time.Duration(2+4) * time.Second))
})
It("uses the minTLPTimeout for short RTTs", func() {
rtt := 2 * time.Microsecond
It("uses the granularity for short RTTs", func() {
rtt := time.Microsecond
updateRTT(rtt)
Expect(handler.computeTLPTimeout()).To(Equal(minTPLTimeout))
})
It("sets the TLP send mode until one retransmittable packet is sent", func() {
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 1, SendTime: time.Now().Add(-time.Hour)}))
handler.OnAlarm()
Expect(handler.SendMode()).To(Equal(SendTLP))
// Send a non-retransmittable packet.
// It doesn't count as a probe packet.
handler.SentPacket(nonRetransmittablePacket(&Packet{PacketNumber: 2}))
Expect(handler.SendMode()).To(Equal(SendTLP))
// Send a retransmittable packet.
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 3}))
Expect(handler.SendMode()).ToNot(Equal(SendTLP))
})
It("sends two TLPs, then RTOs", func() {
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 1, SendTime: time.Now().Add(-time.Hour)}))
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 2, SendTime: time.Now().Add(-time.Hour)}))
// first TLP
handler.OnAlarm()
Expect(handler.SendMode()).To(Equal(SendTLP))
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 3}))
// second TLP
handler.OnAlarm()
Expect(handler.SendMode()).To(Equal(SendTLP))
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 4}))
// fire alarm a third time
handler.OnAlarm()
Expect(handler.SendMode()).To(Equal(SendRTO))
})
})
Context("RTOs", func() {
It("uses default RTO", func() {
Expect(handler.computeRTOTimeout()).To(Equal(defaultRTOTimeout))
})
It("uses RTO from rttStats", func() {
rtt := time.Second
handler.rttStats.UpdateRTT(rtt, 0, time.Now())
Expect(handler.rttStats.SmoothedRTT()).To(Equal(rtt))
Expect(handler.rttStats.MeanDeviation()).To(Equal(rtt / 2))
expected := rtt + rtt/2*4
Expect(handler.computeRTOTimeout()).To(Equal(expected))
})
It("limits RTO min", func() {
rtt := 3 * time.Millisecond
updateRTT(rtt)
Expect(handler.computeRTOTimeout()).To(Equal(minRTOTimeout))
})
It("limits RTO max", func() {
updateRTT(time.Hour)
Expect(handler.computeRTOTimeout()).To(Equal(maxRTOTimeout))
Expect(handler.computePTOTimeout()).To(Equal(granularity))
})
It("implements exponential backoff", func() {
handler.rtoCount = 0
Expect(handler.computeRTOTimeout()).To(Equal(defaultRTOTimeout))
handler.rtoCount = 1
Expect(handler.computeRTOTimeout()).To(Equal(2 * defaultRTOTimeout))
handler.rtoCount = 2
Expect(handler.computeRTOTimeout()).To(Equal(4 * defaultRTOTimeout))
handler.ptoCount = 0
timeout := handler.computePTOTimeout()
Expect(timeout).ToNot(BeZero())
handler.ptoCount = 1
Expect(handler.computePTOTimeout()).To(Equal(2 * timeout))
handler.ptoCount = 2
Expect(handler.computePTOTimeout()).To(Equal(4 * timeout))
})
It("sets the TPO send mode until two packets is sent", func() {
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 1, SendTime: time.Now().Add(-time.Hour)}))
handler.OnAlarm()
Expect(handler.SendMode()).To(Equal(SendPTO))
Expect(handler.ShouldSendNumPackets()).To(Equal(2))
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 2}))
Expect(handler.SendMode()).To(Equal(SendPTO))
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 3}))
Expect(handler.SendMode()).ToNot(Equal(SendPTO))
})
It("only counts retransmittable packets as probe packets", func() {
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 1, SendTime: time.Now().Add(-time.Hour)}))
handler.OnAlarm()
Expect(handler.SendMode()).To(Equal(SendPTO))
Expect(handler.ShouldSendNumPackets()).To(Equal(2))
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 2}))
Expect(handler.SendMode()).To(Equal(SendPTO))
for p := protocol.PacketNumber(3); p < 30; p++ {
handler.SentPacket(nonRetransmittablePacket(&Packet{PacketNumber: p}))
Expect(handler.SendMode()).To(Equal(SendPTO))
}
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 30}))
Expect(handler.SendMode()).ToNot(Equal(SendPTO))
})
It("gets two probe packets if RTO expires", func() {
@@ -755,7 +668,6 @@ var _ = Describe("SentPacketHandler", func() {
updateRTT(time.Hour)
Expect(handler.lossTime.IsZero()).To(BeTrue())
Expect(time.Until(handler.GetAlarmTimeout())).To(BeNumerically("~", handler.computeRTOTimeout(), time.Minute))
handler.OnAlarm() // TLP
handler.OnAlarm() // TLP
@@ -770,7 +682,7 @@ var _ = Describe("SentPacketHandler", func() {
Expect(p.PacketNumber).To(Equal(protocol.PacketNumber(2)))
Expect(handler.bytesInFlight).To(Equal(protocol.ByteCount(2)))
Expect(handler.rtoCount).To(BeEquivalentTo(1))
Expect(handler.ptoCount).To(BeEquivalentTo(3))
})
It("doesn't delete packets transmitted as RTO from the history", func() {
@@ -797,20 +709,6 @@ var _ = Describe("SentPacketHandler", func() {
Expect(handler.retransmissionQueue).To(BeEmpty()) // 1 and 2 were already sent as probe packets
})
It("allows sending of two probe packets", func() {
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 1, SendTime: time.Now().Add(-time.Hour)}))
handler.OnAlarm() // TLP
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 2})) // send the first TLP
handler.OnAlarm() // TLP
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 3})) // send the second TLP
handler.OnAlarm() // RTO
Expect(handler.SendMode()).To(Equal(SendRTO))
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 4}))
Expect(handler.SendMode()).To(Equal(SendRTO))
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 5}))
Expect(handler.SendMode()).ToNot(Equal(SendRTO))
})
It("gets packets sent before the probe packet for retransmission", func() {
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 1, SendTime: time.Now().Add(-time.Hour)}))
handler.SentPacket(retransmittablePacket(&Packet{PacketNumber: 2, SendTime: time.Now().Add(-time.Hour)}))

View File

@@ -830,7 +830,7 @@ sendLoop:
// There will only be a new ACK after receiving new packets.
// SendAck is only returned when we're congestion limited, so we don't need to set the pacingt timer.
return s.maybeSendAckOnlyPacket()
case ackhandler.SendTLP, ackhandler.SendRTO:
case ackhandler.SendPTO:
if err := s.sendProbePacket(); err != nil {
return err
}

View File

@@ -706,7 +706,7 @@ var _ = Describe("Session", func() {
retransmittedPacket := getPacket(123)
sph := mockackhandler.NewMockSentPacketHandler(mockCtrl)
sph.EXPECT().TimeUntilSend()
sph.EXPECT().SendMode().Return(ackhandler.SendTLP)
sph.EXPECT().SendMode().Return(ackhandler.SendPTO)
sph.EXPECT().ShouldSendNumPackets().Return(1)
sph.EXPECT().DequeueProbePacket().Return(packetToRetransmit, nil)
packer.EXPECT().PackRetransmission(packetToRetransmit).Return([]*packedPacket{retransmittedPacket}, nil)