RFC 3448
TCP Friendly Rate Control (TFRC): Protocol Specification
RFC 3448: TCP Friendly Rate Control (TFRC): Protocol Specification ๐
okay bestie, so this is the spec for TFRC, which is a congestion control mechanism that's giving "fair but make it smooth" โจ. It was written by M. Handley, S. Floyd, J. Padhye, and J. Widmer, and it's a Standards Track document, dated January 2003. Its publisher is the Network Working Group. It doesn't obsolete or update any other RFCs, so it's a clean slate. ๐
Quick reminder that the title block is the vibe check: who wrote it, what kind of doc it is (standards track, aka the real deal ๐ ), and when it dropped. ๐ฆ
Status of This Memo ๐ธ
okay so this section is basically the legal disclaimer, but make it cute. This document specifies an Internet standards track protocol for the Internet community, and it's requesting discussion and suggestions for improvements. If you want to know where this protocol stands in the official standardization process, check the current edition of the "Internet Official Protocol Standards" (STD 1). And distribution of this memo is unlimited, so share the tea ๐.
Copyright Notice ๐
Copyright (C) The Internet Society (2003). All Rights Reserved.
Abstract โจ
bestie, here's the main event. This document specifies TCP-Friendly Rate Control (TFRC), which is a congestion control mechanism for unicast flows running in a best-effort Internet environment. It's reasonably fair when it's competing for bandwidth with TCP flows, but it has way less throughput variation over time than TCP does. That makes it perfect for stuff like telephony or streaming media, where a relatively smooth sending rate matters ๐ง๐.
Table of Contents ๐ง
1. Introduction. . . . . . . . . . . . . . . . . . . . . . 2
2. Terminology . . . . . . . . . . . . . . . . . . . . . . 3
3. Protocol Mechanism. . . . . . . . . . . . . . . . . . . 3
3.1. TCP Throughput Equation. . . . . . . . . . . . . . 4
3.2. Packet Contents. . . . . . . . . . . . . . . . . . 6
3.2.1. Data Packets. . . . . . . . . . . . . . . . 6
3.2.2. Feedback Packets. . . . . . . . . . . . . . 7
4. Data Sender Protocol. . . . . . . . . . . . . . . . . . 7
4.1. Measuring the Packet Size. . . . . . . . . . . . . 8
4.2. Sender Initialization. . . . . . . . . . . . . . . 8
4.3. Sender behavior when a feedback packet is
received. . . . . . . . . . . . . .. . . . . . . . 8
4.4. Expiration of nofeedback timer . . . . . . . . . . 9
4.5. Preventing Oscillations. . . . . . . . . . . . . . 10
4.6. Scheduling of Packet Transmissions . . . . . . . . 11
5. Calculation of the Loss Event Rate (p). . . . . . . . . 12
5.1. Detection of Lost or Marked Packets. . . . . . . . 12
5.2. Translation from Loss History to Loss Events . . . 13
5.3. Inter-loss Event Interval. . . . . . . . . . . . . 14
5.4. Average Loss Interval. . . . . . . . . . . . . . . 14
5.5. History Discounting. . . . . . . . . . . . . . . . 15
6. Data Receiver Protocol. . . . . . . . . . . . . . . . . 17
6.1. Receiver behavior when a data packet is
received . . . . . . . . . . . . . . . . . . . . . 18
6.2. Expiration of feedback timer . . . . . . . . . . . 18
6.3. Receiver initialization. . . . . . . . . . . . . . 19
6.3.1. Initializing the Loss History after the
First Loss Event . . . . . . . . . . . . . 19
7. Sender-based Variants . . . . . . . . . . . . . . . . . 20
8. Implementation Issues . . . . . . . . . . . . . . . . . 20
9. Security Considerations . . . . . . . . . . . . . . . . 21
10. IANA Considerations . . . . . . . . . . . . . . . . . . 22
11. Acknowledgments . . . . . . . . . . . . . . . . . . . . 22
12. Non-Normative References. . . . . . . . . . . . . . . . 22
13. Authors' Addresses. . . . . . . . . . . . . . . . . . . 23
14. Full Copyright Statement. . . . . . . . . . . . . . . . 24
1. Introduction ๐ฆ
okay so TFRC is a congestion control mechanism made for unicast flows in the Internet that are competing with TCP traffic [2]. It's not a full protocol, though. Instead, this doc just specifies a congestion control mechanism that could be dropped into a transport protocol like RTP [7], or into an app doing end-to-end congestion control at the application level, or into endpoint congestion management [1]. This document doesn't discuss packet formats or reliability. Implementation stuff only gets a quick mention, in Section 8.
TFRC is designed to be reasonably fair when competing with TCP flows. Here "reasonably fair" means a flow's sending rate is generally within a factor of two of the sending rate of a TCP flow under the same conditions. The big perk is that TFRC has way lower throughput variation over time than TCP, which makes it great for telephony or streaming media where you want a smooth sending rate ๐ .
The catch is that TFRC responds slower than TCP to changes in available bandwidth. So TFRC should only be used when an app really needs smooth throughput, especially if it wants to avoid TCP's halving of the sending rate after a single packet drop ๐. If your app just wants to move as much data as possible as fast as possible, we recommend using TCP. If reliability isn't required, you can use an Additive-Increase, Multiplicative-Decrease (AIMD) congestion control scheme with parameters similar to TCP's.
TFRC is designed for apps that use a fixed packet size and change their sending rate in packets per second in response to congestion. Some audio apps need a fixed interval between packets and instead vary their packet size. The congestion control in this document can't be used by those apps. TFRC-PS (for TFRC-PacketSize) is a variant for apps with a fixed sending rate that vary packet size in response to congestion. TFRC-PS will be specified in a later document ๐ธ.
TFRC is a receiver-based mechanism. The calculation of the congestion control information (the loss event rate) happens in the data receiver, not the data sender. That's a great fit when the sender is a big server handling tons of concurrent connections and the receiver has more memory and CPU to spare. Plus, a receiver-based mechanism is a better building block for multicast congestion control ๐ง.
2. Terminology ๐
In this document, the key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" are to be interpreted as described in BCP 14, RFC 2119 and indicate requirement levels for compliant TFRC implementations.
3. Protocol Mechanism โจ
For its congestion control, TFRC directly uses a throughput equation for the allowed sending rate, as a function of the loss event rate and round-trip time. To compete fairly with TCP, TFRC uses the TCP throughput equation, which roughly describes TCP's sending rate as a function of loss event rate, round-trip time, and packet size. We define a loss event as one or more lost or marked packets from a window of data. A marked packet is a congestion indication from Explicit Congestion Notification (ECN) [6] ๐ฅ.
Generally speaking, TFRC's congestion control works like this:
- The receiver measures the loss event rate and feeds that info back to the sender.
- The sender also uses these feedback messages to measure the round-trip time (RTT).
- The loss event rate and RTT go into TFRC's throughput equation, which gives the acceptable transmit rate.
- The sender then adjusts its transmit rate to match the calculated rate.
The dynamics of TFRC are super sensitive to how the measurements are performed and applied. We recommend specific mechanisms below for doing that. Other mechanisms are possible, but it's important to understand how the interactions between mechanisms affect TFRC's dynamics ๐ง .
3.1. TCP Throughput Equation ๐
okay so any realistic equation that gives TCP throughput as a function of loss event rate and RTT should be suitable for TFRC. But the TCP throughput equation used must reflect TCP's retransmit timeout behavior, since that dominates TCP throughput at higher loss rates ๐. Also, the assumptions the throughput equation makes about the loss event rate parameter have to be a reasonable match for how the loss rate or loss event rate is actually measured. That match isn't perfect for the equation and measurement mechanisms given below, but in practice the assumptions are close enough ๐ซถ.
The throughput equation we currently recommend is a slightly simplified version of the throughput equation for Reno TCP from [4]. Ideally we'd prefer one based on SACK TCP, but nobody has derived the throughput equation for SACK TCP yet. From both simulations and experiments, the differences between the two equations are relatively minor ๐ฆ.
The throughput equation is:
s
X = ----------------------------------------------------------
R*sqrt(2*b*p/3) + (t_RTO * (3*sqrt(3*b*p/8) * p * (1+32*p^2)))
Where:
- X is the transmit rate in bytes/second.
- s is the packet size in bytes.
- R is the round trip time in seconds.
- p is the loss event rate, between 0 and 1.0, of the number of loss events as a fraction of the number of packets transmitted.
- t_RTO is the TCP retransmission timeout value in seconds.
- b is the number of packets acknowledged by a single TCP acknowledgement.
We further simplify this by setting t_RTO = 4*R. A more accurate calculation of t_RTO is possible, but experiments with the current setting have resulted in reasonable fairness with existing TCP implementations [9]. Another possibility would be to set t_RTO = max(4R, one second), to match the recommended minimum of one second on the RTO [5] โจ.
Many current TCP connections use delayed acknowledgements, sending one acknowledgement for every two data packets received, so their sending rate is modeled by b = 2. But TCP is also allowed to send an acknowledgement for every data packet, which would be modeled by b = 1. Because many TCP implementations don't use delayed acknowledgements, we recommend b = 1 ๐.
In the future, different TCP equations may be swapped in for this one. The requirement is that the throughput equation be a reasonable approximation of the sending rate of TCP for conformant TCP congestion control ๐.
The parameters s (packet size), p (loss event rate), and R (RTT) need to be measured or calculated by a TFRC implementation. The measurement of s is specified in Section 4.1, the measurement of R is specified in Section 4.3, and the measurement of p is specified in Section 5. In the rest of this document, all data rates are measured in bytes/second ๐.
To be continued in part 2! ๐ Don't you dare leave me on read, bestie ๐ญ๐
3.2. Packet Contents โจ
okay so before we get into how the sender and receiver actually behave, we need to talk about what's inside the packets they send each other ๐ฆ๐ the sender's data packets and the receiver's feedback packets. TFRC is designed to ride along with a transport protocol, so we're not defining packet formats here ๐ those depend on whichever transport protocol is being used, so it's giving "the details are someone else's problem" energy ๐
3.2.1. Data Packets
every data packet the data sender sends carries the following info ๐ง
- A sequence number. This goes up by one for each data packet transmitted ๐ข The field has to be big enough that it never wraps around and gives you two different packets with the same sequence number sitting in the receiver's recent packet history at the same time ๐ bestie, that would be chaos.
- A timestamp of when the packet was sent. We write this as ts_i for the packet with sequence number i โฐ The resolution should typically be in milliseconds โจ The receiver uses this timestamp to figure out which losses belong to the same loss event ๐ต๏ธโโ๏ธ The sender also gets the timestamp echoed back so it can estimate the round-trip time, which is useful for senders that don't save the timestamps of their transmitted data packets ๐
Here's a fun alternative ๐ฆ instead of a millisecond timestamp, you could use a "timestamp" that ticks every quarter of a round-trip time. That would still be enough to tell when losses belong to the same loss event, as long as both sender and receiver understand the scheme and the sender saves the timestamps of transmitted data packets. Iconic. ๐
- The sender's current round-trip time estimate. The estimate in packet i is written as R_i ๐ธ The receiver uses it, together with the timestamp, to decide when multiple losses belong to the same loss event. If the sender goes with that coarse quarter-RTT "timestamp" from above, then it doesn't need to send its RTT estimate at all ๐ซถ
3.2.2. Feedback Packets
every feedback packet the data receiver sends back contains this ๐
- The timestamp of the last data packet received. We call this t_recvdata ๐ฐ๏ธ If the last packet the receiver got had sequence number i, then t_recvdata = ts_i. The sender uses it to estimate the round-trip time, and it's only needed if the sender doesn't save timestamps of transmitted data packets ๐
- How long it's been between getting the last data packet and making this feedback report. We call this t_delay โณ
- The rate the receiver thinks data has been arriving at since the last feedback report went out. We call this X_recv ๐
- The receiver's current estimate of the loss event rate, p. ๐ p is the main character of this whole thing, honestly ๐
4. Data Sender Protocol ๐ฆ
okay this is where the sender gets to work ๐ The data sender sends a stream of data packets to the data receiver at a controlled rate. Whenever a feedback packet shows up from the receiver, the sender changes its sending rate based on what the feedback says ๐ฌ And if the sender hears nothing back for two round trip times, it cuts its sending rate in half ๐ญ That whole "cut in half when things go quiet" move happens through a timer called the nofeedback timer โฒ๏ธ
Here's what the sender-side protocol covers, step by step:
- Measuring the mean packet size being sent.
- What the sender does when a feedback packet is received.
- What the sender does when the nofeedback timer expires.
- Oscillation prevention (optional) ๐
- Scheduling transmission on non-realtime operating systems ๐ฅ๏ธ
4.1. Measuring the Packet Size ๐
the parameter s (packet size) is normally something the application already knows ๐ But there are two situations where it might not be:
- The packet size naturally changes depending on the data. Even though the size varies, that variation isn't tied to the transmit rate, so it should normally be safe to use an estimate of the mean packet size for s ๐
- The application needs to change the packet size, not the number of packets per second, to do congestion control. This usually happens with packet audio apps, where each packet has to represent a fixed chunk of time โจ These apps need a completely different way of measuring parameters, so it's its own whole thing ๐
The second group gets discussed separately in a different document about TFRC-PS ๐ For the rest of this section, we're assuming the sender can estimate the packet size, and that congestion control happens by adjusting how many packets get sent per second ๐ซถ
4.2. Sender Initialization ๐ฑ
to get the sender set up, X gets set to 1 packet/second and the nofeedback timer gets set to expire after 2 seconds โฐ The starting values for R (RTT) and t_RTO are undefined until they get set the way we describe below ๐คทโโ๏ธ And tld, the Time Last Doubled during slow-start, starts out at -1 ๐
4.3. Sender behavior when a feedback packet is received ๐
the sender knows its current sending rate, X, and keeps track of an estimate of the current round trip time, R, plus an estimate of the timeout interval, t_RTO ๐ง
when a feedback packet arrives at the sender at time t_now, these actions should be performed:
- Calculate a new round trip sample.
R_sample = (t_now - t_recvdata) - t_delay.
- Update the round trip time estimate:
If no feedback has been received before
R = R_sample;
Else
R = q*R + (1-q)*R_sample;
TFRC isn't super sensitive to the exact value of the filter constant q ๐ค but we recommend a default value of 0.9 ๐
- Update the timeout interval:
t_RTO = 4*R.
- Update the sending rate like this:
If (p > 0)
Calculate X_calc using the TCP throughput equation.
X = max(min(X_calc, 2*X_recv), s/t_mbi);
Else
If (t_now - tld >= R)
X = max(min(2*X, 2*X_recv), s/R);
tld = t_now;
quick note: if p == 0, the sender is in slow-start ๐ It roughly doubles its sending rate every round-trip time until a loss happens ๐ The s/R term sets a minimum sending rate during slow-start of one packet per RTT ๐ The parameter t_mbi is 64 seconds, and it represents the maximum inter-packet backoff interval when there's a persistent absence of feedback ๐ So when p > 0, the sender sends at least one packet every 64 seconds, no matter what ๐ญ
- Reset the nofeedback timer to expire after max(4*R, 2*s/X) seconds โฒ๏ธ
4.4. Expiration of nofeedback timer โฐ
if the nofeedback timer expires, the sender should perform these actions:
- Cut the sending rate in half. If the sender has received feedback from the receiver, this happens by changing the sender's cached copy of X_recv (the receive rate) ๐ช Since the sending rate is capped at no more than twice X_recv, changing X_recv limits the current sending rate, but it also lets the sender slow-start, doubling its rate every RTT, if the feedback starts coming back reporting no losses ๐
If (X_calc > 2*X_recv)
X_recv = max(X_recv/2, s/(2*t_mbi));
Else
X_recv = X_calc/4;
the term s/(2*t_mbi) limits the backoff to one packet every 64 seconds when feedback is persistently absent ๐
- X then has to be recalculated the way it's described under point (4) above ๐
If the nofeedback timer expires when the sender doesn't have an RTT sample yet, and it hasn't received any feedback from the receiver, then step (1) can be skipped, and the sending rate gets cut in half directly:
X = max(X/2, s/t_mbi)
- Restart the nofeedback timer to expire after max(4*R, 2*s/X) seconds โฒ๏ธ
Note that when the sender stops sending, the receiver stops sending feedback too ๐คซ That makes the nofeedback timer start to expire, which decreases X_recv ๐ If the sender starts sending again later, X_recv will limit the transmit rate, and a normal slowstart phase happens until the transmit rate reaches X_calc ๐
If the sender has been idle since this nofeedback timer was set, and X_recv is less than four packets per round-trip time, then X_recv should not get halved when the timer expires ๐ This makes sure the allowed sending rate never drops below two packets per round-trip time because of an idle period ๐
4.5. Preventing Oscillations ๐
to keep things from oscillating in environments with a low degree of statistical multiplexing, it's useful to tweak the sender's transmit rate so it behaves like congestion avoidance ๐งโโ๏ธ That means lowering the transmit rate as the queuing delay (and so the RTT) goes up ๐ To do this, the sender keeps an estimate of the long-term RTT, and changes its sending rate based on how the most recent RTT sample compares to that long-term value ๐ The long-term sample is R_sqmean, and it's set like this:
If no feedback has been received before
R_sqmean = sqrt(R_sample);
Else
R_sqmean = q2*R_sqmean + (1-q2)*sqrt(R_sample);
so R_sqmean is the exponentially weighted moving average of the square root of the RTT samples ๐ง The constant q2 should be set similarly to q, and we recommend a default value of 0.9 ๐
The sender gets the base transmit rate, X, from the throughput function ๐งฎ Then it works out a modified instantaneous transmit rate, X_inst, like so:
X_inst = X * R_sqmean / sqrt(R_sample);
when sqrt(R_sample) is greater than R_sqmean, the queue is typically growing, so the transmit rate needs to go down for stable operation ๐
Note: This modification isn't always strictly required, especially when the degree of statistical multiplexing in the network is high ๐ธ But we recommend doing it anyway, because it makes TFRC behave better in environments with a low level of statistical multiplexing ๐ If it isn't done, we recommend using a very low value of q, close to or exactly zero ๐ซถ
4.6. Scheduling of Packet Transmissions โจ
Okay bestie, TFRC is rate-based ๐ฆ, but operating systems can't schedule things super precisely. So the sender has to be opportunistic about sending data packets. That keeps the correct average rate even when the OS scheduling is coarse-grained or irregular ๐
. A typical sending loop works out the right gap between packets, t_ipi, like this:
t_ipi = s/X_inst;
When a sender first starts sending at time t_0, it works out t_ipi and then a nominal send time for packet 1, which is t_1 = t_0 + t_ipi. When the application goes idle, it checks the current time, t_now, and asks to be rescheduled after (t_ipi - (t_now - t_0)) seconds. When the application gets rescheduled, it checks t_now again. If (t_now > t_1 - delta), then packet 1 gets sent ๐ฆโจ
Next, a new t_ipi gets calculated and used to find the nominal send time for packet 2: t2 = t_1 + t_ipi. Then the whole thing repeats, with each packet's send time calculated from the nominal send time of the packet before it. It's giving rhythm ๐
Sometimes, when the nominal send time t_i of the next packet is calculated, it's already true that t_now > t_i - delta. In that case the packet should be sent immediately ๐โโ๏ธ So if the OS has a coarse timer granularity and the transmit rate is high, TFRC may send short bursts of several packets separated by intervals of the OS timer granularity. Bursty but intentional, iykyk ๐ญ
The parameter delta gives a bit of flexibility in when a packet gets sent. If the OS has a scheduling timer granularity of t_gran seconds, then delta would typically be set to:
delta = min(t_ipi/2, t_gran/2);
t_gran is 10ms on many Unix systems. If t_gran isn't known, a value of 10ms can be safely assumed ๐
5. Calculation of the Loss Event Rate (p) ๐ธ
Getting an accurate and stable measurement of the loss event rate is of primary importance for TFRC, no cap ๐ Loss rate measurement happens at the receiver, based on spotting lost or marked packets from the sequence numbers of the packets that arrive. We'll go through this process before we get into the rest of the receiver protocol ๐ง
5.1. Detection of Lost or Marked Packets
TFRC assumes that every packet has a sequence number that goes up by one for each packet sent ๐ข For this spec, if a lost packet gets retransmitted, the retransmission gets a new sequence number, the latest one in the transmission sequence. It must not reuse the sequence number of the packet that was lost ๐ โโ๏ธ If a transport protocol has a requirement that it must retransmit with the original sequence number, then the transport protocol designer has to figure out how to tell delayed packets apart from retransmitted ones, and how to detect lost retransmissions. That's a lot, bestie ๐ญ
The receiver keeps a data structure tracking which packets have arrived and which are missing. For the purposes of this spec, we pretend the data structure is a list of the packets that arrived, along with the receiver timestamp for when each one was received. In real life it'll usually be stored in a more compact form, but how it's stored is implementation-specific ๐
A packet is detected as lost when at least three packets with a higher sequence number than it have arrived ๐ต๏ธโโ๏ธ That three-packet rule matches TCP, and it's there to make TFRC more robust when packets get reordered. Unlike TCP, if a packet shows up late (after three subsequent packets have already arrived), the late packet can fill the hole in TFRC's reception record, and the receiver can recalculate the loss event rate. Plot twist, it's giving redemption arc ๐ฆ Future versions of TFRC might make the three-packet requirement adaptive based on the reordering that's actually being experienced, but this spec doesn't define such a mechanism ๐คทโโ๏ธ
For an ECN-capable connection, a marked packet is detected as a congestion event as soon as it arrives. No need to wait for later packets, periodt ๐ฅ
5.2. Translation from Loss History to Loss Events
TFRC requires the loss fraction to be robust to several consecutive packets being lost when those packets are part of the same loss event. This is kind of like TCP, which (typically) only halves its congestion window once during any single RTT ๐
So the receiver needs to map the packet loss history into a loss event record, where a loss event is one or more packets lost in an RTT. To do this mapping, the receiver needs to know which RTT to use, and the sender supplies that periodically, typically as control information piggy-backed onto a data packet ๐ฆ TFRC isn't sensitive to how the RTT measurement sent to the receiver is made, but we recommend using the sender's calculated RTT, R (see Section 4.3) for this ๐
To figure out whether a lost or marked packet should start a new loss event or count as part of an existing one, we compare the sequence numbers and timestamps of the packets that arrived at the receiver ๐ For a marked packet S_new, its reception time T_new can be noted directly. For a lost packet, we interpolate to infer its nominal "arrival time". Here's the setup:
S_lossis the sequence number of a lost packet.S_beforeis the sequence number of the last packet to arrive with a sequence number beforeS_loss.S_afteris the sequence number of the first packet to arrive with a sequence number afterS_loss.T_beforeis the reception time ofS_before.T_afteris the reception time ofS_after.
Note that T_before can be either before or after T_after because of reordering. Reordering is chaotic but we love a messy queue ๐
For a lost packet S_loss, we can interpolate its nominal "arrival time" at the receiver from the arrival times of S_before and S_after. So:
T_loss = T_before + ( (T_after - T_before)
* (S_loss - S_before)/(S_after - S_before) );
If the sequence space wrapped between S_before and S_after, then the sequence numbers need to be modified to take that into account before doing this calculation ๐ If the largest possible sequence number is S_max, and S_before > S_after, then modifying each sequence number S by S' = (S + (S_max + 1)/2) mod (S_max + 1) would normally be enough โจ
If the lost packet S_old was determined to have started the previous loss event, and we've just determined that S_new has been lost, then we interpolate the nominal arrival times of S_old and S_new, called T_old and T_new respectively ๐ง
If T_old + R >= T_new, then S_new is part of the existing loss event. Otherwise, S_new is the first packet in a new loss event. Main character energy for the new event, honestly ๐
5.3. Inter-loss Event Interval
If a loss interval, A, is determined to have started with packet sequence number S_A, and the next loss interval, B, started with packet sequence number S_B, then the number of packets in loss interval A is given by (S_B - S_A) ๐ Simple math, no drama ๐
5.4. Average Loss Interval
To calculate the loss event rate p, we first calculate the average loss interval. This uses a filter that weights the n most recent loss event intervals in a way that makes the measured loss event rate change smoothly ๐ The weights w_0 to w_(n-1) are calculated as:
If (i < n/2)
w_i = 1;
Else
w_i = 1 - (i - (n/2 - 1))/(n/2 + 1);
So if n=8, the values of w_0 to w_7 are:
1.0, 1.0, 1.0, 1.0, 0.8, 0.6, 0.4, 0.2
The value of n (the number of loss intervals used to calculate the loss event rate) decides how fast TFRC responds to changes in the level of congestion โก As currently specified, TFRC should not be used with values of n significantly greater than 8, for traffic that might compete in the global Internet with TCP. At the very least, safe operation with values of n greater than 8 would need a slight change to TFRC's mechanisms, to include a more severe response to two or more round-trip times with heavy packet loss ๐
When calculating the average loss interval, we need to decide whether to include the interval since the most recent packet loss event. We only include it if it's big enough to increase the average loss interval ๐ฆ
So if the most recent loss intervals are I_0 to I_n, with I_0 being the interval since the most recent loss event, then the average loss interval I_mean is calculated like this:
I_tot0 = 0;
I_tot1 = 0;
W_tot = 0;
for (i = 0 to n-1) {
I_tot0 = I_tot0 + (I_i * w_i);
W_tot = W_tot + w_i;
}
for (i = 1 to n) {
I_tot1 = I_tot1 + (I_i * w_(i-1));
}
I_tot = max(I_tot0, I_tot1);
I_mean = I_tot/W_tot;
And the loss event rate p is simply:
p = 1 / I_mean;
That's the math, bestie, and it's giving precision ๐โจ
5.5. History Discounting
okay so, as we covered in Section 5.4, the most recent loss interval only gets 1/(0.75*n) of the total weight when we average, no matter how big it is ๐ this section is about an optional history discounting mechanism (see [3] and [9] if you want the deep dive). It lets the TFRC receiver shift more of the weight onto that most recent loss interval, but only when it's more than twice the computed average loss interval โจ
To do this, we give each loss interval L_i (for i > 0) a discount factor DF_i. These are floating point numbers, and they're stored in a discount array that keeps the full discounting history for each loss interval ๐ฆ They all start at 1:
for (i = 1 to n) {
DF_i = 1;
}
There's also a general discount factor DF (also a floating point number, also starting at 1) ๐ First we'll look at how the discount factors go into the average loss interval, and then we'll look at how they change over time.
Like in Section 5.4, the average loss interval uses the n previous loss intervals I_1, ..., I_n, plus I_0, which is the number of packets received since the last loss event. Using the discount factors is a small tweak to the Section 5.4 procedure, and it goes like this:
I_tot0 = I_0 * w_0
I_tot1 = 0;
W_tot0 = w_0
W_tot1 = 0;
for (i = 1 to n-1) {
I_tot0 = I_tot0 + (I_i * w_i * DF_i * DF);
W_tot0 = W_tot0 + w_i * DF_i * DF;
}
for (i = 1 to n) {
I_tot1 = I_tot1 + (I_i * w_(i-1) * DF_i);
W_tot1 = W_tot1 + w_(i-1) * DF_i;
}
p = min(W_tot0/I_tot0, W_tot1/I_tot1);
The general discount factor DF gets updated on every packet arrival ๐ฅ First, the receiver works out I_mean, the weighted average of the loss intervals I_1, ..., I_n:
I_tot = 0;
W_tot = 0;
for (i = 1 to n) {
W_tot = W_tot + w_(i-1) * DF_i;
I_tot = I_tot + (I_i * w_(i-1) * DF_i);
}
I_mean = I_tot / W_tot;
Then I_mean gets compared to I_0 (the packets since the last loss event). If I_0 is more than twice I_mean, the new loss interval is way bigger than the old ones, so DF gets updated to lower the weight on the older intervals:
if (I_0 > 2 * I_mean) {
DF = 2 * I_mean/I_0;
if (DF < THRESHOLD)
DF = THRESHOLD;
} else
DF = 1;
THRESHOLD is what stops the older intervals from getting totally discounted. That matters because an old stretch of heavy congestion shouldn't just vanish. We recommend a THRESHOLD of 0.5 ๐ซถ
Every new packet pushes I_0 higher, so DF keeps getting updated as we go ๐
When a new loss event happens, the current interval moves from I_0 into I_1, every I_i slides over to I_(i+1), and I_n gets forgotten ๐ The current DF also has to get folded into the discount array. DF_i holds the discount for I_i, so the DF_i array has to shift along with everything else:
for (i = 1 to n) {
DF_i = DF * DF_i;
}
for (i = n-1 to 0 step -1) {
DF_(i+1) = DF_i;
}
I_0 = 1;
DF_0 = 1;
DF = 1;
And that's the whole optional history discounting mechanism ๐ It's optional, and its only job is to help TFRC respond a bit faster when congestion suddenly goes away, which shows up as one long current loss interval โจ
6. Data Receiver Protocol
The receiver sends feedback messages to the sender on a regular schedule ๐ Feedback packets should normally be sent at least once per RTT, unless the sender is sending at less than one packet per RTT. In that case, a feedback packet should be sent for every data packet received. A feedback packet should also go out right away whenever a new loss event is detected (no waiting for the end of an RTT), and whenever an out-of-order data packet shows up and removes a loss event from the history.
If the sender is going at a high rate (lots of packets per RTT), sending feedback more than once per RTT can help. It lets things respond faster to changing RTT measurements and makes the connection more resilient to feedback packet loss ๐ง Still, sending a huge pile of feedback messages per RTT doesn't gain you much.
6.1. Receiver behavior when a data packet is received
When a data packet comes in, the receiver does the following:
- Add the packet to the packet history.
- Let the previous value of p be p_prev. Calculate the new value of p as described in Section 5.
- If p > p_prev, cause the feedback timer to expire, and perform the actions described in Section 6.2.
If p <= p_prev, no action needs to be performed.
However, an optimization might check whether the packet's arrival filled a hole in the packet history, which merged two loss intervals into one. If that happened, the receiver might also send feedback immediately. The effects of this optimization are normally expected to be small ๐
6.2. Expiration of feedback timer
When the feedback timer at the receiver expires, what happens next depends on whether any data packets came in since the last feedback was sent ๐
Let S_m be the maximum sequence number of a packet the receiver has seen so far, and let R_m be the RTT measurement included in packet S_m. If data packets have arrived since the previous feedback was sent, the receiver does these steps:
- Calculate the average loss event rate using the algorithm described above.
- Calculate the measured receive rate, X_recv, based on the packets received within the previous R_m seconds.
- Prepare and send a feedback packet containing the information described in Section 3.2.2.
- Restart the feedback timer to expire after R_m seconds.
If no data packets have arrived since the last feedback was sent, no feedback packet is sent, and the feedback timer is restarted to expire after R_m seconds ๐
6.3. Receiver initialization
The receiver gets initialized by the first packet that shows up. Let the sequence number of that packet be i.
When the first packet is received:
- Set p=0
- Set X_recv = 0.
- Prepare and send a feedback packet.
- Set the feedback timer to expire after R_i seconds.
6.3.1. Initializing the Loss History after the First Loss Event
You can't use the number of packets before the first loss to compute the sending rate directly, since the sending rate changes super fast during that stretch ๐โโ๏ธ TFRC assumes the right data rate after the first loss is half of the sending rate when the loss happened. To estimate that target, TFRC uses X_recv, the receive rate over the most recent round-trip time. So after the first loss, instead of setting the first loss interval to the number of packets sent until that loss, the TFRC receiver works out the loss interval it would need to produce the data rate X_recv, and uses that synthetic loss interval to seed the loss history mechanism ๐ง
Here's how: TFRC finds some value p for which the throughput equation in Section 3.1 gives a sending rate within 5% of X_recv, given the current packet size s and round-trip time R. The first loss interval is then set to 1/p. (The 5% tolerance is there because the throughput equation is hard to invert, and we want to keep the cost of calculating p numerically down ๐ซง)
7. Sender-based Variants
It would be possible to build a sender-based variant of TFRC. In that version, the receiver uses reliable delivery to tell the sender about packet losses, and the sender calculates the packet loss rate and the acceptable transmit rate itself ๐ฌ This document doesn't specify the details of a sender-based variant, though.
The big upside of a sender-based variant is that the sender wouldn't have to trust the receiver's calculation of the packet loss rate ๐ต๏ธโโ๏ธ The catch is that reliable delivery of loss information from receiver to sender puts much tighter constraints on the transport protocol it's embedded in.
By contrast, the receiver-based variant specified in this document handles lost feedback packets just fine, so it doesn't need reliable delivery of feedback packets ๐ช It's also a better fit for things like streaming media from web servers, where you usually want to offload as much work as possible from the server onto the client ๐
The two variants also upgrade differently. For example, if the procedure for calculating the packet loss rate changes, the sender would need the upgrade in the sender-based variant, and the receiver would need it in the receiver-based variant ๐
8. Implementation Issues
This document specifies the TFRC congestion control mechanism for use by applications and transport protocols ๐ This section briefly mentions a few of the implementation issues.
For t_RTO = 4*R and b = 1, the throughput equation in Section 3.1 can be written like this:
s
X = --------
R * f(p)
where
f(p) = sqrt(2*p/3) + (12*sqrt(3*p/8) * p * (1+32*p^2)).
A table lookup could be used for the function f(p) ๐
Lots of the multiplications (for example, q and 1-q for the round-trip time average, or a factor of 4 for the timeout interval) are, or could be, powers of two. That means they could be done as simple shift operations โก
Note that the optional sender mechanism for preventing oscillations described in Section 4.5 uses a square-root computation ๐งฎ
The calculation of the average loss interval in Section 5.4 multiplies by the weights w_0 to w_(n-1). For n=8, those weights are:
1.0, 1.0, 1.0, 1.0, 0.8, 0.6, 0.4, 0.2.
With a minor loss of smoothness, you could use weights that are powers of two or sums of powers of two, for example:
1.0, 1.0, 1.0, 1.0, 0.75, 0.5, 0.25, 0.25.
The optional history discounting mechanism described in Section 5.5 is used in calculating the average loss rate. It only gets invoked when there has been an unusually long interval with no packet losses ๐ธ For more efficient operation, the discount factor DF_i could be restricted to be a power of two.
9. Security Considerations ๐
Okay bestie, TFRC is not a transport protocol on its own ๐ It's a congestion control mechanism that's meant to be used together with a transport protocol. So security mostly needs to be thought about in the context of whichever transport protocol you're using and its authentication mechanisms ๐ซถ
Congestion control can be exploited to create denial of service ๐ญ One way that happens is through spoofed feedback. So any transport protocol that uses TFRC should take care to make sure feedback is only accepted from the receiver of the data. The precise way to do that depends on the transport protocol itself โจ
Spoofed feedback is the main villain here, so don't skip this part! ๐
Then there's the greedy receiver ๐ This one wants more than its fair share of network bandwidth, and it might try to get it by claiming it received packets that were actually lost due to congestion. Possible defenses would normally include some form of nonce that the receiver has to feed back to the sender to prove it got the packets. But the details of that nonce depend on the transport protocol, and especially on whether the transport protocol is reliable or unreliable ๐ง
We expect that protocols combining ECN with TFRC will also want feedback from the receiver to the sender using the ECN nonce [WES02]. The ECN nonce is a modification to ECN that protects the sender from the accidental or malicious concealment of marked packets ๐ฆ Again, the details of such a nonce would depend on the transport protocol, and they aren't addressed in this document.
10. IANA Considerations ๐
There are no IANA actions required for this document. Nothing to register here, bestie ๐
11. Acknowledgments ๐ซถ
We would like to acknowledge feedback and discussions on equation-based congestion control with a wide range of people, including members of the Reliable Multicast Research Group, the Reliable Multicast Transport Working Group, and the End-to-End Research Group ๐ We would also like to thank Ken Lofgren, Mike Luby, Eduardo Urzaiz, Vladica Stanisic, Randall Stewart, Shushan Wen, and Wendy Lee (lhh@zsu.edu.cn) for feedback on earlier versions of this document ๐ And a huge thank-you to Mark Allman for his extensive feedback from using the document to produce a working implementation ๐
12. Informational References ๐
[1] Balakrishnan, H., Rahul, H., and S. Seshan, "An Integrated Congestion Management Architecture for Internet Hosts," Proc. ACM SIGCOMM, Cambridge, MA, September 1999.
[2] Floyd, S., Handley, M., Padhye, J. and J. Widmer, "Equation-Based Congestion Control for Unicast Applications", August 2000, Proc. ACM SIGCOMM 2000.
[3] Floyd, S., Handley, M., Padhye, J. and J. Widmer, "Equation-Based Congestion Control for Unicast Applications: the Extended Version", ICSI tech report TR-00-03, March 2000.
[4] Padhye, J., Firoiu, V., Towsley, D. and J. Kurose, "Modeling TCP Throughput: A Simple Model and its Empirical Validation", Proc. ACM SIGCOMM 1998.
[5] Paxson V. and M. Allman, "Computing TCP's Retransmission Timer", RFC 2988, November 2000.
[6] Ramakrishnan, K., Floyd, S. and D. Black, "The Addition of Explicit Congestion Notification (ECN) to IP", RFC 3168, September 2001.
[7] Schulzrinne, H., Casner, S., Frederick, R. and V. Jacobson, "RTP: A Transport Protocol for Real-Time Applications", RFC 1889, January 1996.
[8] Wetherall, D., Ely, D., N. Spring, S. Savage, and T. Anderson, "Robust Congestion Signaling", IEEE International Conference on Network Protocols, November 2001.
[9] Widmer, J., "Equation-Based Congestion Control", Diploma Thesis, University of Mannheim, February 2000. URL "http://www.icir.org/tfrc/".
13. Authors' Addresses ๐๏ธ
Mark Handley
ICIR/ICSI
1947 Center St, Suite 600
Berkeley, CA 94708
EMail: mjh@icir.org
Sally Floyd
ICIR/ICSI
1947 Center St, Suite 600
Berkeley, CA 94708
EMail: floyd@icir.org
Jitendra Padhye
Microsoft Research
EMail: padhye@microsoft.com
Joerg Widmer
Lehrstuhl Praktische Informatik IV
Universitat Mannheim
L 15, 16 - Room 415
D-68131 Mannheim
Germany
EMail: widmer@informatik.uni-mannheim.de
14. Full Copyright Statement ยฉ
Copyright (C) The Internet Society (2003). All Rights Reserved. Legally speaking, this is the boring-but-important bit ๐งพ
This document and translations of it may be copied and furnished to others, and derivative works that comment on or otherwise explain it or assist in its implementation may be prepared, copied, published and distributed, in whole or in part, without restriction of any kind, provided that the above copyright notice and this paragraph are included on all such copies and derivative works. However, this document itself may not be modified in any way, such as by removing the copyright notice or references to the Internet Society or other Internet organizations, except as needed for the purpose of developing Internet standards in which case the procedures for copyrights defined in the Internet Standards process must be followed, or as required to translate it into languages other than English. ๐
The limited permissions granted above are perpetual and will not be revoked by the Internet Society or its successors or assigns. โจ
This document and the information contained herein is provided on an "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. ๐
Acknowledgement ๐ธ
Funding for the RFC Editor function is currently provided by the Internet Society. ๐
๐ฆ where this page comes from
- ietf: rfc3448 2003-01
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms