package udt import ( "math" "math/rand" "time" "github.com/PeernetOfficial/core/udt/packet" ) // NativeCongestionControl implements the default congestion control logic for UDP type NativeCongestionControl struct { rcInterval time.Duration // UDT Rate control interval lastRCTime time.Time // last rate increase time slowStart bool // if in slow start phase lastAck packet.PacketID // last ACKed seq no loss bool // if loss happened since last rate increase lastDecSeq packet.PacketID // biggest sequence number when last time the packet sending rate is decreased lastDecPeriod time.Duration // value of PacketSendPeriod when last decrease happened nakCount int // current number of NAKs in the current period decRandom int // random threshold on decrease by number of loss events avgNAKNum int // average number of NAKs in a congestion period decCount int // number of decreases in a congestion epoch } // Init to be called (only) at the start of a UDT connection. func (ncc NativeCongestionControl) Init(parms CongestionControlParms, synTime time.Duration) { ncc.rcInterval = synTime ncc.lastRCTime = time.Now() parms.SetACKPeriod(ncc.rcInterval) ncc.slowStart = true ncc.lastAck = parms.GetSndCurrSeqNo() ncc.loss = false ncc.lastDecSeq = ncc.lastAck.Add(-1) ncc.lastDecPeriod = 1 * time.Microsecond ncc.avgNAKNum = 0 ncc.nakCount = 0 ncc.decRandom = 1 parms.SetCongestionWindowSize(16) parms.SetPacketSendPeriod(1 * time.Microsecond) } // Close to be called when a UDT connection is closed. func (ncc NativeCongestionControl) Close(parms CongestionControlParms) { // nothing done for this event } // OnACK to be called when an ACK packet is received func (ncc NativeCongestionControl) OnACK(parms CongestionControlParms, ack packet.PacketID) { currTime := time.Now() if currTime.Sub(ncc.lastRCTime) < ncc.rcInterval { return } ncc.lastRCTime = currTime cWndSize := parms.GetCongestionWindowSize() pktSendPeriod := parms.GetPacketSendPeriod() recvRate, bandwidth := parms.GetReceiveRates() rtt := parms.GetRTT() // If the current status is in the slow start phase, set the congestion window // size to the product of packet arrival rate and (RTT + SYN). Slow Start ends. Stop. if ncc.slowStart { cWndSize = uint(int(cWndSize) + int(ack.BlindDiff(ncc.lastAck))) ncc.lastAck = ack if cWndSize > parms.GetMaxFlowWindow() { ncc.slowStart = false if recvRate > 0 { parms.SetPacketSendPeriod(time.Second / time.Duration(recvRate)) } else { parms.SetPacketSendPeriod((rtt + ncc.rcInterval) / time.Duration(cWndSize)) } } else { // During Slow Start, no rate increase parms.SetCongestionWindowSize(cWndSize) return } } else { // Set the congestion window size (CWND) to: CWND = A * (RTT + SYN) + 16. cWndSize = uint((float64(recvRate)/float64(time.Second))*float64(rtt+ncc.rcInterval) + 16) } if ncc.loss { ncc.loss = false parms.SetCongestionWindowSize(cWndSize) return } /* The number of sent packets to be increased in the next SYN period (inc) is calculated as: if (B <= C) inc = 1/PS; else inc = max(10^(ceil(log10((B-C)*PS*8))) * Beta/PS, 1/PS); where B is the estimated link capacity and C is the current sending speed. All are counted as packets per second. PS is the fixed size of UDT packet counted in bytes. Beta is a constant value of 0.0000015. */ // Note: 1/24/2012 // The minimum increase parameter is increased from "1.0 / m_iMSS" to 0.01 // because the original was too small and caused sending rate to stay at low level // for long time. var inc float64 const minInc float64 = 0.01 B := time.Duration(bandwidth) - time.Second/time.Duration(pktSendPeriod) bandwidth9 := time.Duration(bandwidth / 9) if (pktSendPeriod > ncc.lastDecPeriod) && (bandwidth9 < B) { B = bandwidth9 } if B <= 0 { inc = minInc } else { // inc = max(10 ^ ceil(log10( B * MSS * 8 ) * Beta / MSS, 1/MSS) // Beta = 1.5 * 10^(-6) mss := parms.GetMSS() inc = math.Pow10(int(math.Ceil(math.Log10(float64(B)*float64(mss)*8.0)))) * 0.0000015 / float64(mss) if inc < minInc { inc = minInc } } // The SND period is updated as: SND = (SND * SYN) / (SND * inc + SYN). parms.SetPacketSendPeriod(time.Duration(float64(pktSendPeriod*ncc.rcInterval) / (float64(pktSendPeriod)*inc + float64(ncc.rcInterval)))) } // OnNAK to be called when a loss report is received func (ncc NativeCongestionControl) OnNAK(parms CongestionControlParms, losslist []packet.PacketID) { // If it is in slow start phase, set inter-packet interval to 1/recvrate. Slow start ends. Stop. if ncc.slowStart { ncc.slowStart = false recvRate, _ := parms.GetReceiveRates() if recvRate > 0 { // Set the sending rate to the receiving rate. parms.SetPacketSendPeriod(time.Second / time.Duration(recvRate)) return } // If no receiving rate is observed, we have to compute the sending // rate according to the current window size, and decrease it // using the method below. parms.SetPacketSendPeriod(time.Duration(float64(time.Microsecond) * float64(parms.GetCongestionWindowSize()) / float64(parms.GetRTT()+ncc.rcInterval))) } ncc.loss = true /* 2) If this NAK starts a new congestion period, increase inter-packet interval (snd) to snd = snd * 1.125; Update AvgNAKNum, reset NAKCount to 1, and compute DecRandom to a random (average distribution) number between 1 and AvgNAKNum. Update LastDecSeq. Stop. 3) If DecCount <= 5, and NAKCount == DecCount * DecRandom: a. Update SND period: SND = SND * 1.125; b. Increase DecCount by 1; c. Record the current largest sent sequence number (LastDecSeq). */ pktSendPeriod := parms.GetPacketSendPeriod() if len(losslist) > 0 && ncc.lastDecSeq.BlindDiff(losslist[0]) > 0 { ncc.lastDecPeriod = pktSendPeriod parms.SetPacketSendPeriod(pktSendPeriod * 1125 / 1000) ncc.avgNAKNum = int(math.Ceil(float64(ncc.avgNAKNum)*0.875 + float64(ncc.nakCount)*0.125)) ncc.nakCount = 1 ncc.decCount = 1 ncc.lastDecSeq = parms.GetSndCurrSeqNo() // remove global synchronization using randomization rand := float64(rand.Uint32()) / math.MaxUint32 ncc.decRandom = int(math.Ceil(float64(ncc.avgNAKNum) * rand)) if ncc.decRandom < 1 { ncc.decRandom = 1 } } else { if ncc.decCount < 5 { ncc.nakCount++ if ncc.decRandom != 0 && ncc.nakCount%ncc.decRandom != 0 { ncc.decCount++ return } } ncc.decCount++ // 0.875^5 = 0.51, rate should not be decreased by more than half within a congestion period parms.SetPacketSendPeriod(pktSendPeriod * 1125 / 1000) ncc.lastDecSeq = parms.GetSndCurrSeqNo() } } // OnTimeout to be called when a timeout event occurs func (ncc NativeCongestionControl) OnTimeout(parms CongestionControlParms) { if ncc.slowStart { ncc.slowStart = false recvRate, _ := parms.GetReceiveRates() if recvRate > 0 { parms.SetPacketSendPeriod(time.Second / time.Duration(recvRate)) } else { parms.SetPacketSendPeriod(time.Duration(float64(time.Microsecond) * float64(parms.GetCongestionWindowSize()) / float64(parms.GetRTT()+ncc.rcInterval))) } } else { /* pktSendPeriod := parms.GetPacketSendPeriod() ncc.lastDecPeriod = pktSendPeriod parms.SetPacketSendPeriod(math.Ceil(pktSendPeriod * 2)) ncc.lastDecSeq = ncc.lastAck */ } } // OnPktSent to be called when data is sent func (ncc NativeCongestionControl) OnPktSent(parms CongestionControlParms, pkt packet.Packet) { // nothing done for this event } // OnPktRecv to be called when a data is received func (ncc NativeCongestionControl) OnPktRecv(parms CongestionControlParms, pkt packet.DataPacket) { // nothing done for this event } // OnCustomMsg to process a user-defined packet func (ncc NativeCongestionControl) OnCustomMsg(parms CongestionControlParms, pkt packet.UserDefControlPacket) { // nothing done for this event }