SACK功能由兩個TCP選項組成,分別爲SACK_PERMITTED和SACK選項。前者用於協商SACK能力,僅可出現在設置了SYN標誌的報文中,如下爲其格式。
TCP Sack-Permitted Option:
Kind: 4
+---------+---------+
| Kind=4 | Length=2|
+---------+---------+
後者用於在連接建立之後傳輸實際的SACK數據,其格式如下。當TCP接收到不連續的數據塊並且加入隊列之後,將發送SACK響應,通知發送端。此選項包含不定長度的塊列表,每個塊描述接收到的數據的首個序號(left)和結尾序號加一(right)。對於n個塊的話,佔用長度爲: 1 kind + 1 length + n*8,由於TCP選項的總長度爲40字節,所以最多可容納4個SACK塊。通常情況下,爲了進行RTTM的測量,TCP報文會攜帶Timestamps選項,其長度爲10個字節,所以SACK選項一般最大爲3個塊。
TCP SACK Option:
Kind: 5
Length: Variable
+--------+--------+
| Kind=5 | Length |
+--------+--------+--------+--------+
| Left Edge of 1st Block |
+--------+--------+--------+--------+
| Right Edge of 1st Block |
+--------+--------+--------+--------+
| |
/ . . . /
| |
+--------+--------+--------+--------+
| Left Edge of nth Block |
+--------+--------+--------+--------+
| Right Edge of nth Block |
+--------+--------+--------+--------+
SACK_PERM協商
可通過PROC文件/proc/sys/net/ipv4/tcp_sack控制對SACK功能的支持,默認情況下tcp_sack爲一。這樣在SYN報文中將攜帶SACK_PERM選項。
static unsigned int tcp_syn_options(struct sock *sk, struct sk_buff *skb,
struct tcp_out_options *opts, struct tcp_md5sig_key **md5)
{
struct tcp_sock *tp = tcp_sk(sk);
if (likely(sock_net(sk)->ipv4.sysctl_tcp_sack)) {
opts->options |= OPTION_SACK_ADVERTISE;
if (unlikely(!(OPTION_TS & opts->options)))
remaining -= TCPOLEN_SACKPERM_ALIGNED;
}
由於SACK_PERM選項佔用2個字節,通常將其與timestamps選項的kind和長度字段(2字節)合併爲一個32bit,但是,如果不支持timestamps選項,將在SACK_PERM選項前添加兩個NOP選項。
static void tcp_options_write(__be32 *ptr, struct tcp_sock *tp, struct tcp_out_options *opts)
{
if (unlikely(OPTION_SACK_ADVERTISE & options)) {
*ptr++ = htonl((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16) |
(TCPOPT_SACK_PERM << 8) | TCPOLEN_SACK_PERM);
}
if (unlikely(opts->num_sack_blocks)) {
...
}
當TCP服務端解析報文的TCP選項字段時,如果當前爲連接建立階段的帶有SYN標誌的報文,並且本端開啓了SACK的支持,設置sack_ok標誌,表示SACK_PERM協商完成。使用函數tcp_sack_reset復位SACK相關記錄。對於SACK選項,如果其長度值合法,並且SACK_PERM協商成功,在TCP控制塊結構成員sacked中保存sack選項首地址相較於TCP頭部的偏移值,將在函數tcp_sacktag_write_queue中依據此偏移值訪問SACK塊數據。
void tcp_parse_options(const struct net *net, const struct sk_buff *skb,
struct tcp_options_received *opt_rx, int estab, struct tcp_fastopen_cookie *foc)
{
switch (opcode) {
case TCPOPT_SACK_PERM:
if (opsize == TCPOLEN_SACK_PERM && th->syn &&
!estab && net->ipv4.sysctl_tcp_sack) {
opt_rx->sack_ok = TCP_SACK_SEEN;
tcp_sack_reset(opt_rx);
}
break;
case TCPOPT_SACK:
if ((opsize >= (TCPOLEN_SACK_BASE + TCPOLEN_SACK_PERBLOCK)) &&
!((opsize - TCPOLEN_SACK_BASE) % TCPOLEN_SACK_PERBLOCK) &&
opt_rx->sack_ok) {
TCP_SKB_CB(skb)->sacked = (ptr - 2) - (unsigned char *)th;
}
break;
同樣,TCP服務端將回復給客戶端的SYNACK報文中添加SACK_PERM字段。客戶端在函數tcp_rcv_synsent_state_process中調用以上介紹的選項解析函數tcp_parse_options,解析SACK_PERM選項,記錄下協商結果(sack_ok)。
static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
tcp_parse_options(sock_net(sk), skb, &tp->rx_opt, 0, &foc);
SACK塊的基本操作
對於SACK塊,內核中分爲兩個類型:SACK塊和重複SACK塊(DSACK)。以下函數tcp_sack_extend,tcp_sack_maybe_coalesce和tcp_sack_remove操作SACK塊;而函數tcp_dsack_set和tcp_dsack_extend負責操作DSACK塊。
函數tcp_sack_extend如下,其嘗試擴展SACK塊的左右邊界。如果當前報文的開始序號在sp塊中記錄的結束序號之前,並且sp塊中記錄的起始序號在報文的結束序號之前,表明兩者的左或者右邊界一定存在交叉部分,或者兩者爲包含關係,進行相應的邊界擴展。
static inline bool tcp_sack_extend(struct tcp_sack_block *sp, u32 seq, u32 end_seq)
{
if (!after(seq, sp->end_seq) && !after(sp->start_seq, end_seq)) {
if (before(seq, sp->start_seq))
sp->start_seq = seq;
if (after(end_seq, sp->end_seq))
sp->end_seq = end_seq;
return true;
}
return false;
}
函數tcp_sack_maybe_coalesce檢查SACK塊是否可進行合併。如以上函數tcp_sack_extend所述,返回值,表明將後一個SACK合併到了sp中,將SACK塊數量減一,並將之後的SACK塊向前移動。
static void tcp_sack_maybe_coalesce(struct tcp_sock *tp)
{
int this_sack;
struct tcp_sack_block *sp = &tp->selective_acks[0];
struct tcp_sack_block *swalk = sp + 1;
for (this_sack = 1; this_sack < tp->rx_opt.num_sacks;) {
if (tcp_sack_extend(sp, swalk->start_seq, swalk->end_seq)) {
int i;
/* Zap SWALK, by moving every further SACK up by one slot. Decrease num_sacks.
*/
tp->rx_opt.num_sacks--;
for (i = this_sack; i < tp->rx_opt.num_sacks; i++)
sp[i] = sp[i + 1];
continue;
}
this_sack++, swalk++;
}
}
函數tcp_sack_remove移除SACK塊,遍歷selective_acks數組,找到在RCV.NXT之前的SACK塊,如果SACK塊僅有一部分在RCV.NXT之前,發出警告。如果SACK塊的起止序號都在RCV.NXT之前,表明已經接收到了此塊數據,隨將數組中之後的SACK塊向前移動,覆蓋此SACK塊,達到刪除的效果。
/* RCV.NXT advances, some SACKs should be eaten. */
static void tcp_sack_remove(struct tcp_sock *tp)
{
struct tcp_sack_block *sp = &tp->selective_acks[0];
int num_sacks = tp->rx_opt.num_sacks;
for (this_sack = 0; this_sack < num_sacks;) {
/* Check if the start of the sack is covered by RCV.NXT. */
if (!before(tp->rcv_nxt, sp->start_seq)) {
WARN_ON(before(tp->rcv_nxt, sp->end_seq)); /* RCV.NXT must cover all the block! */
/* Zap this SACK, by moving forward any other SACKS. */
for (i = this_sack+1; i < num_sacks; i++)
tp->selective_acks[i-1] = tp->selective_acks[i];
num_sacks--;
continue;
}
this_sack++;
sp++;
}
tp->rx_opt.num_sacks = num_sacks;
如下爲DSACK的操作函數tcp_dsack_set,其前提是連接協商完成了對SACK_PERM的支持,並且系統開啓了對重複DSACK的支持,將重複的起止序號保存在套接口的duplicate_sack結構中。這裏duplicate_sack數組的最大長度爲一。
static void tcp_dsack_set(struct sock *sk, u32 seq, u32 end_seq)
{
struct tcp_sock *tp = tcp_sk(sk);
if (tcp_is_sack(tp) && sock_net(sk)->ipv4.sysctl_tcp_dsack) {
int mib_idx;
if (before(seq, tp->rcv_nxt))
mib_idx = LINUX_MIB_TCPDSACKOLDSENT;
else
mib_idx = LINUX_MIB_TCPDSACKOFOSENT;
NET_INC_STATS(sock_net(sk), mib_idx);
tp->rx_opt.dsack = 1;
tp->duplicate_sack[0].start_seq = seq;
tp->duplicate_sack[0].end_seq = end_seq;
}
}
如下DSACK塊的擴展函數tcp_dsack_extend,如果dsack爲0,調用以上介紹的函數tcp_dsack_set添加dsack塊。否則,借用SACK的函數tcp_sack_extend擴展dsack塊。
static void tcp_dsack_extend(struct sock *sk, u32 seq, u32 end_seq)
{
struct tcp_sock *tp = tcp_sk(sk);
if (!tp->rx_opt.dsack)
tcp_dsack_set(sk, seq, end_seq);
else
tcp_sack_extend(tp->duplicate_sack, seq, end_seq);
}
報文驗證與DSACK
如下接收驗證函數tcp_validate_incoming中,如果PAWS驗證沒有通過,並且此報文沒有設置RST標誌位,調用函數tcp_send_dupack發送DSACK。或者接收報文的序號不可接受,並且未設置SYN標誌(SYNACK報文),調用函數tcp_send_dupack發送DSACK。
static bool tcp_validate_incoming(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th, int syn_inerr)
{
struct tcp_sock *tp = tcp_sk(sk);
bool rst_seq_match = false;
/* RFC1323: H1. Apply PAWS check first. */
if (tcp_fast_parse_options(sock_net(sk), skb, th, tp) && tp->rx_opt.saw_tstamp &&
tcp_paws_discard(sk, skb)) {
if (!th->rst) {
if (!tcp_oow_rate_limited(sock_net(sk), skb,
LINUX_MIB_TCPACKSKIPPEDPAWS, &tp->last_oow_ack_time))
tcp_send_dupack(sk, skb);
goto discard;
}
/* Step 1: check sequence number */
if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {
if (!th->rst) {
if (th->syn)
goto syn_challenge;
if (!tcp_oow_rate_limited(sock_net(sk), skb,
LINUX_MIB_TCPACKSKIPPEDSEQ, &tp->last_oow_ack_time))
tcp_send_dupack(sk, skb);
以下驗證接收到的RST報文,如果RST的序號不等於RCV.NXT,但是等於SACK塊中記錄的最大序號(max_sack),那麼也認爲此RST報文有效,復位連接。
if (th->rst) {
if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt || tcp_reset_check(sk, skb)) {
rst_seq_match = true;
} else if (tcp_is_sack(tp) && tp->rx_opt.num_sacks > 0) {
struct tcp_sack_block *sp = &tp->selective_acks[0];
int max_sack = sp[0].end_seq;
for (this_sack = 1; this_sack < tp->rx_opt.num_sacks; ++this_sack) {
max_sack = after(sp[this_sack].end_seq, max_sack) ?
sp[this_sack].end_seq : max_sack;
}
if (TCP_SKB_CB(skb)->seq == max_sack) rst_seq_match = true;
}
if (rst_seq_match) tcp_reset(sk);
如上所示,內核使用tcp_send_dupack函數發送DSACK,其首先確認是否接收到重複數據,之後計算重複數據的起止序號,設置DSACK塊,調用tcp_send_ack執行發送操作。
static void tcp_send_dupack(struct sock *sk, const struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
if (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&
before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {
if (tcp_is_sack(tp) && sock_net(sk)->ipv4.sysctl_tcp_dsack) {
u32 end_seq = TCP_SKB_CB(skb)->end_seq;
tcp_rcv_spurious_retrans(sk, skb);
if (after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt))
end_seq = tp->rcv_nxt;
tcp_dsack_set(sk, TCP_SKB_CB(skb)->seq, end_seq);
}
}
tcp_send_ack(sk);
}
數據報文接收與SACK/DSACK發送
如下數據接收函數tcp_data_queue,如果報文的起始序號seq等於當前套接口的下一個等待接收序號rcv_nxt,表明接收到一個保序的報文。在接收完成之後,如果存在SACK塊,調用tcp_sack_remove嘗試刪除已經接收到其序號範圍的SACK塊。
static void tcp_data_queue(struct sock *sk, struct sk_buff *skb)
{
if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt) {
eaten = tcp_queue_rcv(sk, skb, &fragstolen);
if (skb->len)
tcp_event_data_recv(sk, skb);
if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)
tcp_fin(sk);
if (!RB_EMPTY_ROOT(&tp->out_of_order_queue)) {
tcp_ofo_queue(sk);
/* RFC5681. 4.2. SHOULD send immediate ACK, when gap in queue is filled.
*/
if (RB_EMPTY_ROOT(&tp->out_of_order_queue))
inet_csk(sk)->icsk_ack.pending |= ICSK_ACK_NOW;
}
if (tp->rx_opt.num_sacks)
tcp_sack_remove(tp);
return;
}
如果報文的結束序號end_seq在當前套接口的下一個等待接收序號rcv_nxt之前,表明接收到一個重複的報文。使用函數tcp_dsack_set將其起止序號添加到DSACK塊中。
if (!after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt)) {
tcp_rcv_spurious_retrans(sk, skb);
/* A retransmit, 2nd most common case. Force an immediate ack. */
NET_INC_STATS(sock_net(sk), LINUX_MIB_DELAYEDACKLOST);
tcp_dsack_set(sk, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq);
out_of_window:
tcp_enter_quickack_mode(sk, TCP_MAX_QUICKACKS);
inet_csk_schedule_ack(sk);
drop:
tcp_drop(sk, skb);
return;
}
另外,如果以上不成立,結束序號在rcv_nxt之後,但是報文的開始序號seq在rcv_nxt之前,表明接收到的報文的前段有重複的數據,長度爲rcv_nxt - seq,將相應重複的序號設置到DSACK塊中。
/* Out of window. F.e. zero window probe. */
if (!before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt + tcp_receive_window(tp)))
goto out_of_window;
if (before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {
/* Partial packet, seq < rcv_next < end_seq */
SOCK_DEBUG(sk, "partial packet: rcv_next %X seq %X - %X\n",
tp->rcv_nxt, TCP_SKB_CB(skb)->seq,
TCP_SKB_CB(skb)->end_seq);
tcp_dsack_set(sk, TCP_SKB_CB(skb)->seq, tp->rcv_nxt);
goto queue_and_out;
}
tcp_data_queue_ofo(sk, skb);
最後,在函數tcp_data_queue中,如果接收到的爲保序的報文,將其放入接收隊列,並且更新rcv_nxt序號。如果套接口亂序隊列不爲空,需檢查是否可將其中的數據移至接收隊列。如下函數tcp_ofo_queue,遍歷亂序隊列,如果其中的報文起始序號在rcv_nxt之前,表明此報文與剛剛接收的報文有重複數據,計算重複數據的起止序號,添加到DSACK塊中(tcp_dsack_extend)。
static void tcp_ofo_queue(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
__u32 dsack_high = tp->rcv_nxt;
p = rb_first(&tp->out_of_order_queue);
while (p) {
skb = rb_to_skb(p);
if (after(TCP_SKB_CB(skb)->seq, tp->rcv_nxt))
break;
if (before(TCP_SKB_CB(skb)->seq, dsack_high)) {
__u32 dsack = dsack_high;
if (before(TCP_SKB_CB(skb)->end_seq, dsack_high))
dsack_high = TCP_SKB_CB(skb)->end_seq;
tcp_dsack_extend(sk, TCP_SKB_CB(skb)->seq, dsack);
}
p = rb_next(p);
rb_erase(&skb->rbnode, &tp->out_of_order_queue);
如果亂序隊列中包含rcv_nxt序號之後的連續的數據,將報文數據添加到接收隊列(sk_receive_queue),並且更新rcv_nxt,繼續以上的過程。
if (unlikely(!after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt))) {
SOCK_DEBUG(sk, "ofo packet was already received\n");
tcp_drop(sk, skb);
continue;
}
SOCK_DEBUG(sk, "ofo requeuing : rcv_next %X seq %X - %X\n",
tp->rcv_nxt, TCP_SKB_CB(skb)->seq,
TCP_SKB_CB(skb)->end_seq);
tail = skb_peek_tail(&sk->sk_receive_queue);
eaten = tail && tcp_try_coalesce(sk, tail, skb, &fragstolen);
tcp_rcv_nxt_update(tp, TCP_SKB_CB(skb)->end_seq);
fin = TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN;
if (!eaten)
__skb_queue_tail(&sk->sk_receive_queue, skb);
else
kfree_skb_partial(skb, fragstolen);
亂序報文與SACK/DSACK
以下函數tcp_data_queue_ofo處理亂序的TCP報文,如果套接口的亂序隊列爲空,並且SACK_PERM協商成功,添加SACK塊到selective_acks數組中(index爲0)。
static void tcp_data_queue_ofo(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
if (unlikely(tcp_try_rmem_schedule(sk, skb, skb->truesize))) {
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPOFODROP);
tcp_drop(sk, skb);
return;
}
p = &tp->out_of_order_queue.rb_node;
if (RB_EMPTY_ROOT(&tp->out_of_order_queue)) { /* Initial out of order segment, build 1 SACK. */
if (tcp_is_sack(tp)) {
tp->rx_opt.num_sacks = 1;
tp->selective_acks[0].start_seq = seq;
tp->selective_acks[0].end_seq = end_seq;
}
rb_link_node(&skb->rbnode, NULL, p);
rb_insert_color(&skb->rbnode, &tp->out_of_order_queue);
tp->ooo_last_skb = skb;
goto end;
}
如果當前報文skb正好可與亂序隊列的最後一個報文序號連着一起,將其合併到最後一個報文,跳到增加sack塊處執行。
/* In the typical case, we are adding an skb to the end of the list.
* Use of ooo_last_skb avoids the O(Log(N)) rbtree lookup.
*/
if (tcp_ooo_try_coalesce(sk, tp->ooo_last_skb, skb, &fragstolen)) {
coalesce_done:
tcp_grow_window(sk, skb);
kfree_skb_partial(skb, fragstolen);
skb = NULL;
goto add_sack;
}
如果當前報文的起始序號seq在亂序隊列的最後一個報文的結束序號之後,直接指向插入操作。
/* Can avoid an rbtree lookup if we are adding skb after ooo_last_skb */
if (!before(seq, TCP_SKB_CB(tp->ooo_last_skb)->end_seq)) {
parent = &tp->ooo_last_skb->rbnode;
p = &parent->rb_right;
goto insert;
}
以下情況表明當前報文位於亂序隊列的中部,如果亂序隊列中的報文skb1完全的包含當前報文的數據,丟棄當前報文,設置重複DSACK,參見函數tcp_dsack_set。
/* Find place to insert this segment. Handle overlaps on the way. */
parent = NULL;
while (*p) {
parent = *p;
skb1 = rb_to_skb(parent);
if (before(seq, TCP_SKB_CB(skb1)->seq)) {
p = &parent->rb_left;
continue;
}
if (before(seq, TCP_SKB_CB(skb1)->end_seq)) {
if (!after(end_seq, TCP_SKB_CB(skb1)->end_seq)) {
/* All the bits are present. Drop. */
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPOFOMERGE);
tcp_drop(sk, skb);
skb = NULL;
tcp_dsack_set(sk, seq, end_seq);
goto add_sack;
}
如果當前報文與亂序隊列中的報文skb1部分重疊,同樣需要設置DSACK。否則,當前報文包含亂序隊列中的報文skb1,替換亂序隊列中的報文skb1,設置擴展DSACK,參見函數tcp_dsack_extend。
if (after(seq, TCP_SKB_CB(skb1)->seq)) {
/* Partial overlap. */
tcp_dsack_set(sk, seq, TCP_SKB_CB(skb1)->end_seq);
} else {
/* skb's seq == skb1's seq and skb covers skb1. Replace skb1 with skb.
*/
rb_replace_node(&skb1->rbnode, &skb->rbnode, &tp->out_of_order_queue);
tcp_dsack_extend(sk,
TCP_SKB_CB(skb1)->seq, TCP_SKB_CB(skb1)->end_seq);
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPOFOMERGE);
tcp_drop(sk, skb1);
goto merge_right;
}
}
由於以上在將當前報文插入亂序隊列的過程中,可能有重疊情況的發生,在插入之後,清除重複的報文,並且更新擴展DSACK塊中的序號範圍。
/* Insert segment into RB tree. */
rb_link_node(&skb->rbnode, parent, p);
rb_insert_color(&skb->rbnode, &tp->out_of_order_queue);
merge_right:
/* Remove other segments covered by skb. */
while ((skb1 = skb_rb_next(skb)) != NULL) {
if (!after(end_seq, TCP_SKB_CB(skb1)->seq))
break;
if (before(end_seq, TCP_SKB_CB(skb1)->end_seq)) {
tcp_dsack_extend(sk, TCP_SKB_CB(skb1)->seq, end_seq);
break;
}
rb_erase(&skb1->rbnode, &tp->out_of_order_queue);
tcp_dsack_extend(sk, TCP_SKB_CB(skb1)->seq, TCP_SKB_CB(skb1)->end_seq);
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPOFOMERGE);
tcp_drop(sk, skb1);
}
具體的SACK塊添加操作由函數tcp_sack_new_ofo_skb完成。
add_sack:
if (tcp_is_sack(tp))
tcp_sack_new_ofo_skb(sk, seq, end_seq);
如果SACK塊列表中存在與當前報文的起止序號交叉的塊,擴展列表中的響應SACK塊,參見以上介紹的tcp_sack_extend函數,之後,將擴展的SACK塊移動到列表的首位。如果列表中SACK塊數量大於一,調用以上介紹的函數tcp_sack_maybe_coalesce嘗試進行合併。
static void tcp_sack_new_ofo_skb(struct sock *sk, u32 seq, u32 end_seq)
{
struct tcp_sock *tp = tcp_sk(sk);
struct tcp_sack_block *sp = &tp->selective_acks[0];
int cur_sacks = tp->rx_opt.num_sacks;
if (!cur_sacks) goto new_sack;
for (this_sack = 0; this_sack < cur_sacks; this_sack++, sp++) {
if (tcp_sack_extend(sp, seq, end_seq)) {
/* Rotate this_sack to the first one. */
for (; this_sack > 0; this_sack--, sp--)
swap(*sp, *(sp - 1));
if (cur_sacks > 1)
tcp_sack_maybe_coalesce(tp);
return;
}
}
否則,如果沒有在列表中找到相鄰的SACK塊,創建一個新的SACK塊,當SACK塊數量大於等於TCP_NUM_SACKS(4)的時候,丟棄列表末端的SACK塊。並將之前的SACK塊全部向後移動,將新SACK塊添加在列表前端。
if (this_sack >= TCP_NUM_SACKS) {
if (tp->compressed_ack > TCP_FASTRETRANS_THRESH)
tcp_send_ack(sk);
this_sack--;
tp->rx_opt.num_sacks--;
sp--;
}
for (; this_sack > 0; this_sack--, sp--)
*sp = *(sp - 1);
new_sack:
/* Build the new head SACK, and we're done. */
sp->start_seq = seq;
sp->end_seq = end_seq;
tp->rx_opt.num_sacks++;
}
在函數tcp_data_queue_ofo的開始部分調用tcp_try_rmem_schedule函數,處理TCP協議rmem短缺的問題,可能需要釋放掉亂序隊列中的報文,以滿足空間需求,如下函數tcp_prune_ofo_queue,在釋放亂序隊列(部分或者全部)之後,清空全部的SACK塊。
static bool tcp_prune_ofo_queue(struct sock *sk)
{
...
/* Reset SACK state. A conforming SACK implementation will
* do the same at a timeout based retransmit. When a connection
* is in a sad state like this, we care only about integrity
* of the connection not performance.
*/
if (tp->rx_opt.sack_ok)
tcp_sack_reset(&tp->rx_opt);
return true;
SACK選項發送
如下函數tcp_established_options所示,SACK的數量由SACK和DSACK的數量組成。但是,總的SACK佔用的空間不超出TCP選項字段剩餘的空間。
static unsigned int tcp_established_options(struct sock *sk, struct sk_buff *skb,
struct tcp_out_options *opts, struct tcp_md5sig_key **md5)
{
eff_sacks = tp->rx_opt.num_sacks + tp->rx_opt.dsack;
if (unlikely(eff_sacks)) {
const unsigned int remaining = MAX_TCP_OPTION_SPACE - size;
opts->num_sack_blocks = min_t(unsigned int, eff_sacks,
(remaining - TCPOLEN_SACK_BASE_ALIGNED) / TCPOLEN_SACK_PERBLOCK);
size += TCPOLEN_SACK_BASE_ALIGNED +
opts->num_sack_blocks * TCPOLEN_SACK_PERBLOCK;
}
如下所示SACK選項賦值時,優先發送DSACK塊的內容,之後是SACK列表的塊內容。在內存中,duplicate_sack數組和selective_acks數組連在一起。最後,因爲DSACK只有一個塊,發送之後將其數量清空。
static void tcp_options_write(__be32 *ptr, struct tcp_sock *tp, struct tcp_out_options *opts)
{
if (unlikely(opts->num_sack_blocks)) {
struct tcp_sack_block *sp = tp->rx_opt.dsack ?
tp->duplicate_sack : tp->selective_acks;
int this_sack;
*ptr++ = htonl((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16) |
(TCPOPT_SACK << 8) |
(TCPOLEN_SACK_BASE + (opts->num_sack_blocks *
TCPOLEN_SACK_PERBLOCK)));
for (this_sack = 0; this_sack < opts->num_sack_blocks;
++this_sack) {
*ptr++ = htonl(sp[this_sack].start_seq);
*ptr++ = htonl(sp[this_sack].end_seq);
}
tp->rx_opt.dsack = 0;
}
內核版本 5.0