Linux tcp_ack 确认处理与 sack_update_reord 重排判断tcp_ack 是 TCP 输入路径的确认处理核心函数在 tcp_rcv_established 和 tcp_rcv_state_process 中被调用。该函数处理传入 ACK 段所携带的累积确认和选择确认SACK信息负责更新发送窗口、调整拥塞状态、触发 cwnd 恢复以及判断报文重排序程度。tcp_ack 的返回值是累计确认的报文段数量packets_acked为拥塞控制算法提供输入。cstatic int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag){struct tcp_sock *tp tcp_sk(sk);u32 prior_snd_una tp-snd_una;u32 ack_seq TCP_SKB_CB(skb)-end_seq;u32 ack TCP_SKB_CB(skb)-seq;u32 prior_packets tp-packets_out;int acked 0;if (after(ack, tp-snd_una)) {acked tcp_clean_rtx_queue(sk, prior_fackets, prior_snd_una, sack_state);} else if (ack tp-snd_una) {if (flag FLAG_DSACKING_ACK)tcp_dsack_process(sk, skb, sack_state);tcp_update_wl(tp, ack);}if (tp-packets_out 0 flag (FLAG_SYN_ACKED | FLAG_DATA_ACKED))tcp_cong_control(sk, ack, flg, sack_state.rate, acked);...}tcp_clean_rtx_queue 是确认处理的核心——遍历重传队列sk_write_queue将 end_seq 落在 tcp_skb_cb-seq 到 TCP_SKB_CB(skb)-end_seq 范围内的 skb 移出队列。每个 skb 的 tcp_skb_cb-sacked 标志位决定该 skb 被确认、SACKed、还是 LOST。对于 ACK 确认的 skb调用 __skb_unlink 从重传队列移除并通过 sk_wmem_free_skb 释放 skb。acks 状态机需谨慎处理 D-SACK 检测当 ACK 确认了一段已经 SACKed 的数据tp-duplicate_sack[0] 记录第一个 D-SACK block用于计算 spurious retransmit。SACK 块在 tcp_sacktag_write_queue 中处理该函数被 tcp_ack 调用。每个 SACK block 表示接收端已收到的乱序数据区间。函数从 sk_write_queue 的 tcp_highest_sack 起始遍历标识被 SACK block 覆盖的 skb。cstatic void tcp_sacktag_write_queue(struct sock *sk,const struct sk_buff *ack_skb,u32 prior_snd_una,struct tcp_sacktag_state *state){struct tcp_sock *tp tcp_sk(sk);struct sk_buff *skb;int found_dup_sack 0;int i, first_sack_index;if (!tp-rx_opt.num_sacks)return;state-flag 0;state-fack_count 0;skb tcp_write_queue_head(sk);while (skb skb ! tcp_write_queue_tail(sk)) {if (!tcp_skb_is_write_queue(sk, skb))break;...skb skb-next;}}sack_update_reord 评估重排序程度。当发现 SACK block 覆盖的序列号区间早于已确认数据的末尾则该 SACK 块对应的 skb 在发送路径中序号连续但接收端乱序到达表明发生了 reordering。该函数的核心逻辑是如果被 SACK 确认的 skb 的 TCP_SKB_CB(skb)-seq 小于 tp-snd_una累积确认序列号则该数据段在发送缓冲区中的位置早于最新确认的数据发生了重排。cstatic void sack_update_reord(struct tcp_sock *tp,struct tcp_sacktag_state *state,u32 seq, u32 mss){if (seq_cmp(tp-snd_una, seq) 0) {if (!tp-reord || seq_cmp(tp-reord, seq) 0) {tp-reord seq;state-reord true;}}}tp-reord 字段记录重排序边界用于区分丢包和乱序。如果 ACK/SACK 确认的 skb 序列号大于等于 tp-reord则不触发快速重传。tp-reord 在 tcp_mark_head_lost 中同样被用于丢包判决。这里的关键竞态当接收端延迟发送 SACKDelayed SACK时sender 可能将正常乱序误判为丢包导致 spurious RTO。reord 的初始值为 tp-snd_nxt 2 * MSS在三次握手完成后初始化。tcp_ack 中的 flag 位掩码传递关键信息FLAG_SND_UNA_ADVANCED、FLAG_DATA_ACKED、FLAG_SYN_ACKED、FLAG_DSACKING_ACK、FLAG_ECE、FLAG_LOST_RETRANS 等。flag 通过 tcp_sacktag_write_queue 和 tcp_clean_rtx_queue 累积生成最终传递到 tcp_cong_control。FLAG_ECE 标志决定是否启用 ECN 信号触发 cwnd 的减半而非丢包事件。cstatic void tcp_cong_control(struct sock *sk, u32 ack, u32 flag,const struct rate_sample *rs,int acked){if (flag FLAG_ECE)tcp_enter_cwr(sk);else if (flag FLAG_SND_UNA_ADVANCED)tcp_cwnd_reduction(sk, ack, rs, flag, acked);else if (flag FLAG_DATA_ACKED)tcp_cwnd_application_limited(sk);}dupack重复 ACK计数由 tcp_ack 中的 tp-snd_una 不变但 packets_out 不为零的情形触发。当 tp-snd_una 不前进且 SACK 不含新信息tp-packets_out 与 prior_packets 相等时记录 dupthresh由 RFC 6675 定义为 3。在 reordering 场景下优先使用 SACK 判定而非 dupack 计数跟踪 skb-sacked TCPCB_SACKED_ACKED 的位状态结合 fackets_out由 SACK 确认的 skb 数量决定是否调用 tcp_time_to_recover。一个边界场景是 SACK 块覆盖的数据已经被快速重传此时 tcp_mark_head_lost 可能因为 reord 信息不准确而产生额外的 LOST 标记导致 cwnd 虚幻收缩。为避免此问题tcp_clean_rtx_queue 清除 TCPCB_SACKED_RETRANS 位时需同步检查 seq 是否落入 retrans_hint 区域。如果 retransmitted skb 被确认但未被 sender 记录为有效的 LOSTtcp_sacktag_write_queue 的回溯检测会重置 retrans_stamp防止 RTO 过早触发。
Linux tcp_ack 确认处理与 sack_update_reord 重排判断
Linux tcp_ack 确认处理与 sack_update_reord 重排判断tcp_ack 是 TCP 输入路径的确认处理核心函数在 tcp_rcv_established 和 tcp_rcv_state_process 中被调用。该函数处理传入 ACK 段所携带的累积确认和选择确认SACK信息负责更新发送窗口、调整拥塞状态、触发 cwnd 恢复以及判断报文重排序程度。tcp_ack 的返回值是累计确认的报文段数量packets_acked为拥塞控制算法提供输入。cstatic int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag){struct tcp_sock *tp tcp_sk(sk);u32 prior_snd_una tp-snd_una;u32 ack_seq TCP_SKB_CB(skb)-end_seq;u32 ack TCP_SKB_CB(skb)-seq;u32 prior_packets tp-packets_out;int acked 0;if (after(ack, tp-snd_una)) {acked tcp_clean_rtx_queue(sk, prior_fackets, prior_snd_una, sack_state);} else if (ack tp-snd_una) {if (flag FLAG_DSACKING_ACK)tcp_dsack_process(sk, skb, sack_state);tcp_update_wl(tp, ack);}if (tp-packets_out 0 flag (FLAG_SYN_ACKED | FLAG_DATA_ACKED))tcp_cong_control(sk, ack, flg, sack_state.rate, acked);...}tcp_clean_rtx_queue 是确认处理的核心——遍历重传队列sk_write_queue将 end_seq 落在 tcp_skb_cb-seq 到 TCP_SKB_CB(skb)-end_seq 范围内的 skb 移出队列。每个 skb 的 tcp_skb_cb-sacked 标志位决定该 skb 被确认、SACKed、还是 LOST。对于 ACK 确认的 skb调用 __skb_unlink 从重传队列移除并通过 sk_wmem_free_skb 释放 skb。acks 状态机需谨慎处理 D-SACK 检测当 ACK 确认了一段已经 SACKed 的数据tp-duplicate_sack[0] 记录第一个 D-SACK block用于计算 spurious retransmit。SACK 块在 tcp_sacktag_write_queue 中处理该函数被 tcp_ack 调用。每个 SACK block 表示接收端已收到的乱序数据区间。函数从 sk_write_queue 的 tcp_highest_sack 起始遍历标识被 SACK block 覆盖的 skb。cstatic void tcp_sacktag_write_queue(struct sock *sk,const struct sk_buff *ack_skb,u32 prior_snd_una,struct tcp_sacktag_state *state){struct tcp_sock *tp tcp_sk(sk);struct sk_buff *skb;int found_dup_sack 0;int i, first_sack_index;if (!tp-rx_opt.num_sacks)return;state-flag 0;state-fack_count 0;skb tcp_write_queue_head(sk);while (skb skb ! tcp_write_queue_tail(sk)) {if (!tcp_skb_is_write_queue(sk, skb))break;...skb skb-next;}}sack_update_reord 评估重排序程度。当发现 SACK block 覆盖的序列号区间早于已确认数据的末尾则该 SACK 块对应的 skb 在发送路径中序号连续但接收端乱序到达表明发生了 reordering。该函数的核心逻辑是如果被 SACK 确认的 skb 的 TCP_SKB_CB(skb)-seq 小于 tp-snd_una累积确认序列号则该数据段在发送缓冲区中的位置早于最新确认的数据发生了重排。cstatic void sack_update_reord(struct tcp_sock *tp,struct tcp_sacktag_state *state,u32 seq, u32 mss){if (seq_cmp(tp-snd_una, seq) 0) {if (!tp-reord || seq_cmp(tp-reord, seq) 0) {tp-reord seq;state-reord true;}}}tp-reord 字段记录重排序边界用于区分丢包和乱序。如果 ACK/SACK 确认的 skb 序列号大于等于 tp-reord则不触发快速重传。tp-reord 在 tcp_mark_head_lost 中同样被用于丢包判决。这里的关键竞态当接收端延迟发送 SACKDelayed SACK时sender 可能将正常乱序误判为丢包导致 spurious RTO。reord 的初始值为 tp-snd_nxt 2 * MSS在三次握手完成后初始化。tcp_ack 中的 flag 位掩码传递关键信息FLAG_SND_UNA_ADVANCED、FLAG_DATA_ACKED、FLAG_SYN_ACKED、FLAG_DSACKING_ACK、FLAG_ECE、FLAG_LOST_RETRANS 等。flag 通过 tcp_sacktag_write_queue 和 tcp_clean_rtx_queue 累积生成最终传递到 tcp_cong_control。FLAG_ECE 标志决定是否启用 ECN 信号触发 cwnd 的减半而非丢包事件。cstatic void tcp_cong_control(struct sock *sk, u32 ack, u32 flag,const struct rate_sample *rs,int acked){if (flag FLAG_ECE)tcp_enter_cwr(sk);else if (flag FLAG_SND_UNA_ADVANCED)tcp_cwnd_reduction(sk, ack, rs, flag, acked);else if (flag FLAG_DATA_ACKED)tcp_cwnd_application_limited(sk);}dupack重复 ACK计数由 tcp_ack 中的 tp-snd_una 不变但 packets_out 不为零的情形触发。当 tp-snd_una 不前进且 SACK 不含新信息tp-packets_out 与 prior_packets 相等时记录 dupthresh由 RFC 6675 定义为 3。在 reordering 场景下优先使用 SACK 判定而非 dupack 计数跟踪 skb-sacked TCPCB_SACKED_ACKED 的位状态结合 fackets_out由 SACK 确认的 skb 数量决定是否调用 tcp_time_to_recover。一个边界场景是 SACK 块覆盖的数据已经被快速重传此时 tcp_mark_head_lost 可能因为 reord 信息不准确而产生额外的 LOST 标记导致 cwnd 虚幻收缩。为避免此问题tcp_clean_rtx_queue 清除 TCPCB_SACKED_RETRANS 位时需同步检查 seq 是否落入 retrans_hint 区域。如果 retransmitted skb 被确认但未被 sender 记录为有效的 LOSTtcp_sacktag_write_queue 的回溯检测会重置 retrans_stamp防止 RTO 过早触发。