主要涉及到兩個變量,一是重傳報文計數retrans_out;二是丟失報文計數lost_out。
RACK丟失報文判斷
如下函數tcp_rack_detect_loss,如果報文具有丟失標誌(TCPCB_LOST),但是沒有重傳標誌(TCPCB_SACKED_RETRANS),表明丟失報文還未進行重傳,不進行重複處理。否則,在RACK確認報文已經丟失之後,由函數tcp_mark_skb_lost進行丟失標記。
static void tcp_rack_detect_loss(struct sock *sk, u32 *reo_timeout)
{
*reo_timeout = 0;
reo_wnd = tcp_rack_reo_wnd(sk);
list_for_each_entry_safe(skb, n, &tp->tsorted_sent_queue, tcp_tsorted_anchor) {
struct tcp_skb_cb *scb = TCP_SKB_CB(skb);
/* Skip ones marked lost but not yet retransmitted */
if ((scb->sacked & TCPCB_LOST) &&
!(scb->sacked & TCPCB_SACKED_RETRANS))
continue;
if (!tcp_rack_sent_after(tp->rack.mstamp,
tcp_skb_timestamp_us(skb),
tp->rack.end_seq, scb->end_seq))
break;
/* A packet is lost if it has not been s/acked beyond
* the recent RTT plus the reordering window.
*/
remaining = tcp_rack_skb_timeout(tp, skb, reo_wnd);
if (remaining <= 0) {
tcp_mark_skb_lost(sk, skb);
在函數tcp_mark_skb_lost中,如果重傳報文(標誌TCPCB_SACKED_RETRANS)丟失,清除其重傳標誌TCPCB_SACKED_RETRANS,以便進行下一次重傳。在以上tcp_rack_detect_loss丟失檢測中,可見,對此種丟失未重傳報文不進行重複處理。
注意,這裏對重傳丟失的報文,減少重傳報文計數retrans_out。以下將會看到Reno在標記丟包時,不處理retrans_out計數。
void tcp_mark_skb_lost(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
tcp_skb_mark_lost_uncond_verify(tp, skb);
if (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_RETRANS) {
TCP_SKB_CB(skb)->sacked &= ~TCPCB_SACKED_RETRANS;
tp->retrans_out -= tcp_skb_pcount(skb);
與Reno不同,RACK(或者SACK)可標記重傳隊列中多個報文爲丟失狀態(TCPCB_LOST),Reno僅能表示重傳隊列首個報文。因而,在重傳時,RACK(或SACK)也可重傳多個丟失的報文。參見以下重傳函數tcp_xmit_retransmit_queue中的判斷,retrans_out不能大於lost_out,不能重傳大於丟失數量的報文。
void tcp_xmit_retransmit_queue(struct sock *sk)
{
rtx_head = tcp_rtx_queue_head(sk);
skb = tp->retransmit_skb_hint ?: rtx_head;
max_segs = tcp_tso_segs(sk, tcp_current_mss(sk));
skb_rbtree_walk_from(skb) {
...
segs = tp->snd_cwnd - tcp_packets_in_flight(tp);
if (segs <= 0) return;
sacked = TCP_SKB_CB(skb)->sacked;
/* In case tcp_shift_skb_data() have aggregated large skbs,
* we need to make sure not sending too bigs TSO packets
*/
segs = min_t(int, segs, max_segs);
if (tp->retrans_out >= tp->lost_out) {
break;
...
if (tcp_retransmit_skb(sk, skb, segs)) return;
Reno丟包計數
以下爲Reno算法的丟包標記函數tcp_newreno_mark_lost,與RACK(或者SACK)不同,Reno沒有足夠的信息判斷多個報文的丟失情況,根據重複ACK(dupack)僅能判斷,SND.UNA序號開始的報文(重傳隊列首報文)丟失。所以,如果重傳隊列首個skb,包含多個報文,或者其長度大於MSS值,進行分片,TCPCB_LOST丟包標誌只能設置於第一個長度不大於MSS的報文。
這裏也就不需要像以上的RACK算法,或者SACK算法遍歷重傳隊列,Reno調用tcp_skb_mark_lost_uncond_verify函數標記重傳隊列首報文(可能是分片後報文)丟失即可。
另外Reno與RACK/SACK的不同在於,後者在tcp_mark_skb_lost函數中檢測重傳報文的丟失,並且減少重傳報文計數,而Reno不進行處理。Reno僅在接收到確認ACK(無論是確認原始還是重傳報文)時將retrans_out減一。
void tcp_newreno_mark_lost(struct sock *sk, bool snd_una_advanced)
{
const u8 state = inet_csk(sk)->icsk_ca_state;
struct tcp_sock *tp = tcp_sk(sk);
if ((state < TCP_CA_Recovery && tp->sacked_out >= tp->reordering) ||
(state == TCP_CA_Recovery && snd_una_advanced)) {
struct sk_buff *skb = tcp_rtx_queue_head(sk);
u32 mss;
if (TCP_SKB_CB(skb)->sacked & TCPCB_LOST)
return;
mss = tcp_skb_mss(skb);
if (tcp_skb_pcount(skb) > 1 && skb->len > mss)
tcp_fragment(sk, TCP_FRAG_IN_RTX_QUEUE, skb,
mss, mss, GFP_ATOMIC);
tcp_skb_mark_lost_uncond_verify(tp, skb);
如下tcp_skb_mark_lost_uncond_verify,如果報文沒有被確認丟失並且也沒有被對端SACK確認接收,增加lost_out丟包計數,並且設置標誌TCPCB_LOST。對於Reno而言,不可能被SACK確認,並且在以上函數tcp_newreno_mark_lost中,也已經確認了報文還沒有設置TCPCB_LOST標誌,所以這裏的if判斷必定成立。
另外,由於Reno僅標記一個報文,lost_out值遞增1。而且在以上函數中判斷只要首報文被設置了TCPCB_LOST標誌,不繼續重複標記,因而,lost_out的值實際上總是1。
void tcp_skb_mark_lost_uncond_verify(struct tcp_sock *tp, struct sk_buff *skb)
{
tcp_verify_retransmit_hint(tp, skb);
tcp_sum_lost(tp, skb);
if (!(TCP_SKB_CB(skb)->sacked & (TCPCB_LOST|TCPCB_SACKED_ACKED))) {
tp->lost_out += tcp_skb_pcount(skb);
TCP_SKB_CB(skb)->sacked |= TCPCB_LOST;
接下來看一看Reno的丟失計數lost_out的減少。在接收到ACK確認報文後,清理重傳隊列函數tcp_clean_rtx_queue中,如果檢測到報文設置了TCPCB_LOST標誌,將丟包計數lost_out減去相應的報文數量。對於Reno而言,僅有可能在重傳隊列的首報文中設置TCPCB_LOST標誌。由於在設置TCPCB_LOST標誌時,確保了重傳隊列第一個skb僅包含一個報文,這裏lost_out相當於遞減1,其值變爲0。
注意,由於被對端確認接收,此報文將由重傳隊列中移除,這樣隊列中的下一個報文(成爲首報文)纔有可能被標記爲TCPCB_LOST,參見以上函數tcp_newreno_mark_lost。
static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack,
u32 prior_snd_una, struct tcp_sacktag_state *sack)
{
for (skb = skb_rb_first(&sk->tcp_rtx_queue); skb; skb = next) {
struct tcp_skb_cb *scb = TCP_SKB_CB(skb);
u8 sacked = scb->sacked;
if (after(scb->end_seq, tp->snd_una)) {
...
} else {
acked_pcount = tcp_skb_pcount(skb);
}
if (sacked & TCPCB_LOST)
tp->lost_out -= acked_pcount;
Reno重傳計數
在報文重傳函數tcp_retransmit_skb中,重傳成功之後,更新重傳計數,由於在上節介紹的tcp_newreno_mark_lost函數中,將重傳隊列首個skb分片爲單報文,此處retrans_out相當於遞增1。並且爲報文增加重傳標誌TCPCB_RETRANS。
int tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb, int segs)
{
struct tcp_sock *tp = tcp_sk(sk);
int err = __tcp_retransmit_skb(sk, skb, segs);
if (err == 0) {
#if FASTRETRANS_DEBUG > 0
if (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_RETRANS) {
net_dbg_ratelimited("retrans_out leaked\n");
}
#endif
TCP_SKB_CB(skb)->sacked |= TCPCB_RETRANS;
tp->retrans_out += tcp_skb_pcount(skb);
在以上函數tcp_retransmit_skb的調用函數tcp_xmit_retransmit_queue中,如果重傳的報文數量retrans_out大於等於丟失報文數量lost_out,停止遍歷重傳隊列。對於Reno,丟失報文計數lost_out爲1,僅允許重傳一個報文。此後,retrans_out的值也增加爲1。
void tcp_xmit_retransmit_queue(struct sock *sk)
{
rtx_head = tcp_rtx_queue_head(sk);
skb = tp->retransmit_skb_hint ?: rtx_head;
max_segs = tcp_tso_segs(sk, tcp_current_mss(sk));
skb_rbtree_walk_from(skb) {
...
segs = tp->snd_cwnd - tcp_packets_in_flight(tp);
if (segs <= 0) return;
sacked = TCP_SKB_CB(skb)->sacked;
/* In case tcp_shift_skb_data() have aggregated large skbs,
* we need to make sure not sending too bigs TSO packets
*/
segs = min_t(int, segs, max_segs);
if (tp->retrans_out >= tp->lost_out) {
break;
...
if (tcp_retransmit_skb(sk, skb, segs)) return;
在接收到ACK確認報文後,清理重傳隊列函數tcp_clean_rtx_queue中,如果報文設置了重傳標誌TCPCB_RETRANS,將retrans_out減少相應的報文數量,對於Reno而言,相當於遞減1。同時在此函數中,也將丟失報文數量lost_out減去1。
static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack,
u32 prior_snd_una, struct tcp_sacktag_state *sack)
{
for (skb = skb_rb_first(&sk->tcp_rtx_queue); skb; skb = next) {
struct tcp_skb_cb *scb = TCP_SKB_CB(skb);
u8 sacked = scb->sacked;
if (after(scb->end_seq, tp->snd_una)) {
...
} else {
acked_pcount = tcp_skb_pcount(skb);
}
if (unlikely(sacked & TCPCB_RETRANS)) {
if (sacked & TCPCB_SACKED_RETRANS)
tp->retrans_out -= acked_pcount;
flag |= FLAG_RETRANS_DATA_ACKED;
...
if (sacked & TCPCB_LOST)
tp->lost_out -= acked_pcount;
內核版本 5.0