mirror of
https://github.com/v2fly/v2ray-core.git
synced 2024-11-07 10:47:48 -05:00
319 lines
10 KiB
Go
319 lines
10 KiB
Go
package congestion
|
|
|
|
import (
|
|
"time"
|
|
|
|
"v2ray.com/core/external/github.com/lucas-clemente/quic-go/internal/protocol"
|
|
"v2ray.com/core/external/github.com/lucas-clemente/quic-go/internal/utils"
|
|
)
|
|
|
|
const (
|
|
maxBurstBytes = 3 * protocol.DefaultTCPMSS
|
|
renoBeta float32 = 0.7 // Reno backoff factor.
|
|
defaultMinimumCongestionWindow protocol.ByteCount = 2 * protocol.DefaultTCPMSS
|
|
)
|
|
|
|
type cubicSender struct {
|
|
hybridSlowStart HybridSlowStart
|
|
prr PrrSender
|
|
rttStats *RTTStats
|
|
stats connectionStats
|
|
cubic *Cubic
|
|
|
|
reno bool
|
|
|
|
// Track the largest packet that has been sent.
|
|
largestSentPacketNumber protocol.PacketNumber
|
|
|
|
// Track the largest packet that has been acked.
|
|
largestAckedPacketNumber protocol.PacketNumber
|
|
|
|
// Track the largest packet number outstanding when a CWND cutback occurs.
|
|
largestSentAtLastCutback protocol.PacketNumber
|
|
|
|
// Whether the last loss event caused us to exit slowstart.
|
|
// Used for stats collection of slowstartPacketsLost
|
|
lastCutbackExitedSlowstart bool
|
|
|
|
// When true, exit slow start with large cutback of congestion window.
|
|
slowStartLargeReduction bool
|
|
|
|
// Congestion window in packets.
|
|
congestionWindow protocol.ByteCount
|
|
|
|
// Minimum congestion window in packets.
|
|
minCongestionWindow protocol.ByteCount
|
|
|
|
// Maximum congestion window.
|
|
maxCongestionWindow protocol.ByteCount
|
|
|
|
// Slow start congestion window in bytes, aka ssthresh.
|
|
slowstartThreshold protocol.ByteCount
|
|
|
|
// Number of connections to simulate.
|
|
numConnections int
|
|
|
|
// ACK counter for the Reno implementation.
|
|
numAckedPackets uint64
|
|
|
|
initialCongestionWindow protocol.ByteCount
|
|
initialMaxCongestionWindow protocol.ByteCount
|
|
|
|
minSlowStartExitWindow protocol.ByteCount
|
|
}
|
|
|
|
var _ SendAlgorithm = &cubicSender{}
|
|
var _ SendAlgorithmWithDebugInfo = &cubicSender{}
|
|
|
|
// NewCubicSender makes a new cubic sender
|
|
func NewCubicSender(clock Clock, rttStats *RTTStats, reno bool, initialCongestionWindow, initialMaxCongestionWindow protocol.ByteCount) SendAlgorithmWithDebugInfo {
|
|
return &cubicSender{
|
|
rttStats: rttStats,
|
|
initialCongestionWindow: initialCongestionWindow,
|
|
initialMaxCongestionWindow: initialMaxCongestionWindow,
|
|
congestionWindow: initialCongestionWindow,
|
|
minCongestionWindow: defaultMinimumCongestionWindow,
|
|
slowstartThreshold: initialMaxCongestionWindow,
|
|
maxCongestionWindow: initialMaxCongestionWindow,
|
|
numConnections: defaultNumConnections,
|
|
cubic: NewCubic(clock),
|
|
reno: reno,
|
|
}
|
|
}
|
|
|
|
// TimeUntilSend returns when the next packet should be sent.
|
|
func (c *cubicSender) TimeUntilSend(bytesInFlight protocol.ByteCount) time.Duration {
|
|
if c.InRecovery() {
|
|
// PRR is used when in recovery.
|
|
if c.prr.CanSend(c.GetCongestionWindow(), bytesInFlight, c.GetSlowStartThreshold()) {
|
|
return 0
|
|
}
|
|
}
|
|
delay := c.rttStats.SmoothedRTT() / time.Duration(2*c.GetCongestionWindow())
|
|
if !c.InSlowStart() { // adjust delay, such that it's 1.25*cwd/rtt
|
|
delay = delay * 8 / 5
|
|
}
|
|
return delay
|
|
}
|
|
|
|
func (c *cubicSender) OnPacketSent(
|
|
sentTime time.Time,
|
|
bytesInFlight protocol.ByteCount,
|
|
packetNumber protocol.PacketNumber,
|
|
bytes protocol.ByteCount,
|
|
isRetransmittable bool,
|
|
) {
|
|
if !isRetransmittable {
|
|
return
|
|
}
|
|
if c.InRecovery() {
|
|
// PRR is used when in recovery.
|
|
c.prr.OnPacketSent(bytes)
|
|
}
|
|
c.largestSentPacketNumber = packetNumber
|
|
c.hybridSlowStart.OnPacketSent(packetNumber)
|
|
}
|
|
|
|
func (c *cubicSender) InRecovery() bool {
|
|
return c.largestAckedPacketNumber <= c.largestSentAtLastCutback && c.largestAckedPacketNumber != 0
|
|
}
|
|
|
|
func (c *cubicSender) InSlowStart() bool {
|
|
return c.GetCongestionWindow() < c.GetSlowStartThreshold()
|
|
}
|
|
|
|
func (c *cubicSender) GetCongestionWindow() protocol.ByteCount {
|
|
return c.congestionWindow
|
|
}
|
|
|
|
func (c *cubicSender) GetSlowStartThreshold() protocol.ByteCount {
|
|
return c.slowstartThreshold
|
|
}
|
|
|
|
func (c *cubicSender) ExitSlowstart() {
|
|
c.slowstartThreshold = c.congestionWindow
|
|
}
|
|
|
|
func (c *cubicSender) SlowstartThreshold() protocol.ByteCount {
|
|
return c.slowstartThreshold
|
|
}
|
|
|
|
func (c *cubicSender) MaybeExitSlowStart() {
|
|
if c.InSlowStart() && c.hybridSlowStart.ShouldExitSlowStart(c.rttStats.LatestRTT(), c.rttStats.MinRTT(), c.GetCongestionWindow()/protocol.DefaultTCPMSS) {
|
|
c.ExitSlowstart()
|
|
}
|
|
}
|
|
|
|
func (c *cubicSender) OnPacketAcked(
|
|
ackedPacketNumber protocol.PacketNumber,
|
|
ackedBytes protocol.ByteCount,
|
|
priorInFlight protocol.ByteCount,
|
|
eventTime time.Time,
|
|
) {
|
|
c.largestAckedPacketNumber = utils.MaxPacketNumber(ackedPacketNumber, c.largestAckedPacketNumber)
|
|
if c.InRecovery() {
|
|
// PRR is used when in recovery.
|
|
c.prr.OnPacketAcked(ackedBytes)
|
|
return
|
|
}
|
|
c.maybeIncreaseCwnd(ackedPacketNumber, ackedBytes, priorInFlight, eventTime)
|
|
if c.InSlowStart() {
|
|
c.hybridSlowStart.OnPacketAcked(ackedPacketNumber)
|
|
}
|
|
}
|
|
|
|
func (c *cubicSender) OnPacketLost(
|
|
packetNumber protocol.PacketNumber,
|
|
lostBytes protocol.ByteCount,
|
|
priorInFlight protocol.ByteCount,
|
|
) {
|
|
// TCP NewReno (RFC6582) says that once a loss occurs, any losses in packets
|
|
// already sent should be treated as a single loss event, since it's expected.
|
|
if packetNumber <= c.largestSentAtLastCutback {
|
|
if c.lastCutbackExitedSlowstart {
|
|
c.stats.slowstartPacketsLost++
|
|
c.stats.slowstartBytesLost += lostBytes
|
|
if c.slowStartLargeReduction {
|
|
// Reduce congestion window by lost_bytes for every loss.
|
|
c.congestionWindow = utils.MaxByteCount(c.congestionWindow-lostBytes, c.minSlowStartExitWindow)
|
|
c.slowstartThreshold = c.congestionWindow
|
|
}
|
|
}
|
|
return
|
|
}
|
|
c.lastCutbackExitedSlowstart = c.InSlowStart()
|
|
if c.InSlowStart() {
|
|
c.stats.slowstartPacketsLost++
|
|
}
|
|
|
|
c.prr.OnPacketLost(priorInFlight)
|
|
|
|
// TODO(chromium): Separate out all of slow start into a separate class.
|
|
if c.slowStartLargeReduction && c.InSlowStart() {
|
|
if c.congestionWindow >= 2*c.initialCongestionWindow {
|
|
c.minSlowStartExitWindow = c.congestionWindow / 2
|
|
}
|
|
c.congestionWindow -= protocol.DefaultTCPMSS
|
|
} else if c.reno {
|
|
c.congestionWindow = protocol.ByteCount(float32(c.congestionWindow) * c.RenoBeta())
|
|
} else {
|
|
c.congestionWindow = c.cubic.CongestionWindowAfterPacketLoss(c.congestionWindow)
|
|
}
|
|
if c.congestionWindow < c.minCongestionWindow {
|
|
c.congestionWindow = c.minCongestionWindow
|
|
}
|
|
c.slowstartThreshold = c.congestionWindow
|
|
c.largestSentAtLastCutback = c.largestSentPacketNumber
|
|
// reset packet count from congestion avoidance mode. We start
|
|
// counting again when we're out of recovery.
|
|
c.numAckedPackets = 0
|
|
}
|
|
|
|
func (c *cubicSender) RenoBeta() float32 {
|
|
// kNConnectionBeta is the backoff factor after loss for our N-connection
|
|
// emulation, which emulates the effective backoff of an ensemble of N
|
|
// TCP-Reno connections on a single loss event. The effective multiplier is
|
|
// computed as:
|
|
return (float32(c.numConnections) - 1. + renoBeta) / float32(c.numConnections)
|
|
}
|
|
|
|
// Called when we receive an ack. Normal TCP tracks how many packets one ack
|
|
// represents, but quic has a separate ack for each packet.
|
|
func (c *cubicSender) maybeIncreaseCwnd(
|
|
ackedPacketNumber protocol.PacketNumber,
|
|
ackedBytes protocol.ByteCount,
|
|
priorInFlight protocol.ByteCount,
|
|
eventTime time.Time,
|
|
) {
|
|
// Do not increase the congestion window unless the sender is close to using
|
|
// the current window.
|
|
if !c.isCwndLimited(priorInFlight) {
|
|
c.cubic.OnApplicationLimited()
|
|
return
|
|
}
|
|
if c.congestionWindow >= c.maxCongestionWindow {
|
|
return
|
|
}
|
|
if c.InSlowStart() {
|
|
// TCP slow start, exponential growth, increase by one for each ACK.
|
|
c.congestionWindow += protocol.DefaultTCPMSS
|
|
return
|
|
}
|
|
// Congestion avoidance
|
|
if c.reno {
|
|
// Classic Reno congestion avoidance.
|
|
c.numAckedPackets++
|
|
// Divide by num_connections to smoothly increase the CWND at a faster
|
|
// rate than conventional Reno.
|
|
if c.numAckedPackets*uint64(c.numConnections) >= uint64(c.congestionWindow)/uint64(protocol.DefaultTCPMSS) {
|
|
c.congestionWindow += protocol.DefaultTCPMSS
|
|
c.numAckedPackets = 0
|
|
}
|
|
} else {
|
|
c.congestionWindow = utils.MinByteCount(c.maxCongestionWindow, c.cubic.CongestionWindowAfterAck(ackedBytes, c.congestionWindow, c.rttStats.MinRTT(), eventTime))
|
|
}
|
|
}
|
|
|
|
func (c *cubicSender) isCwndLimited(bytesInFlight protocol.ByteCount) bool {
|
|
congestionWindow := c.GetCongestionWindow()
|
|
if bytesInFlight >= congestionWindow {
|
|
return true
|
|
}
|
|
availableBytes := congestionWindow - bytesInFlight
|
|
slowStartLimited := c.InSlowStart() && bytesInFlight > congestionWindow/2
|
|
return slowStartLimited || availableBytes <= maxBurstBytes
|
|
}
|
|
|
|
// BandwidthEstimate returns the current bandwidth estimate
|
|
func (c *cubicSender) BandwidthEstimate() Bandwidth {
|
|
srtt := c.rttStats.SmoothedRTT()
|
|
if srtt == 0 {
|
|
// If we haven't measured an rtt, the bandwidth estimate is unknown.
|
|
return 0
|
|
}
|
|
return BandwidthFromDelta(c.GetCongestionWindow(), srtt)
|
|
}
|
|
|
|
// HybridSlowStart returns the hybrid slow start instance for testing
|
|
func (c *cubicSender) HybridSlowStart() *HybridSlowStart {
|
|
return &c.hybridSlowStart
|
|
}
|
|
|
|
// SetNumEmulatedConnections sets the number of emulated connections
|
|
func (c *cubicSender) SetNumEmulatedConnections(n int) {
|
|
c.numConnections = utils.Max(n, 1)
|
|
c.cubic.SetNumConnections(c.numConnections)
|
|
}
|
|
|
|
// OnRetransmissionTimeout is called on an retransmission timeout
|
|
func (c *cubicSender) OnRetransmissionTimeout(packetsRetransmitted bool) {
|
|
c.largestSentAtLastCutback = 0
|
|
if !packetsRetransmitted {
|
|
return
|
|
}
|
|
c.hybridSlowStart.Restart()
|
|
c.cubic.Reset()
|
|
c.slowstartThreshold = c.congestionWindow / 2
|
|
c.congestionWindow = c.minCongestionWindow
|
|
}
|
|
|
|
// OnConnectionMigration is called when the connection is migrated (?)
|
|
func (c *cubicSender) OnConnectionMigration() {
|
|
c.hybridSlowStart.Restart()
|
|
c.prr = PrrSender{}
|
|
c.largestSentPacketNumber = 0
|
|
c.largestAckedPacketNumber = 0
|
|
c.largestSentAtLastCutback = 0
|
|
c.lastCutbackExitedSlowstart = false
|
|
c.cubic.Reset()
|
|
c.numAckedPackets = 0
|
|
c.congestionWindow = c.initialCongestionWindow
|
|
c.slowstartThreshold = c.initialMaxCongestionWindow
|
|
c.maxCongestionWindow = c.initialMaxCongestionWindow
|
|
}
|
|
|
|
// SetSlowStartLargeReduction allows enabling the SSLR experiment
|
|
func (c *cubicSender) SetSlowStartLargeReduction(enabled bool) {
|
|
c.slowStartLargeReduction = enabled
|
|
}
|