TCP发送数据包后,会收到对端的ACK。通过处理ACK,TCP可以进行拥塞控制和流控制,所以

ACK的处理是TCP的一个重要内容。tcp_ack()用于处理接收到的ACK。

本文主要内容:TCP接收ACK处理,tcp_ack()的实现。

内核版本:3.2.12

Author:zhangskd @ csdn

基础

在我们开始探究tcp_ack()的处理流程前,不妨先来回顾一些即将涉及到的概念和数据结构。

struct tcp_sock {
...
/* Packets which are "in flight",发送且未确认的数据包个数*/
u32 packets_out ;
/* Retransmitted packets out ,重传的且未确认数据包个数 */
u32 retrans_out ;
/* 被SACKED数据段的个数 */
u32 sacked_out ;
/* 丢失的数据包个数 */
u32 loss_out ;
/* FACK'd packets*/
u32 fackets_out;
/* snd_nxt at onset of congestion */
u32 high_seq;
u32 bytes_acked; /* Appropriate Byte Counting - RFC3465*/
u32 prior_ssthresh; /* ssthresh saved at recovery start */
u8 reordering; /* Packet reordering metric. */ /* Options received (usually on last paket, some only on SYN packets).*/
struct tcp_options_received rx_opt;
u32 snd_wl1; /* Sequence for window update,记录更新发送窗口的ACK段序号*/
...
}

TCP的控制信息块:

/* This is what the send packet queuing engine uses to pass
* TCP per-packet control information to the transmission code.
* We also store the host-order sequence numbers in here too.
* This is 44 bytes if IPv6 is enabled.
* If this grows please adjust sk_buff.h : skbuff->cb[xxx] size appropriately.
*/
struct tcp_skb_cb {
union {
struct inet_skb_parm h4 ;
#if defined (CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
struct inet6_skb_parm h6 ;
#endif
} header ; /* For incoming frames ,主要是ip头信息*/ __u32 seq ; /* Starting sequence number */
__u32 end_seq ; /* SEQ + FIN + SYN + datalen */
__u32 when ; /* used to compute rtt's */
__u8 flags ; /* TCP header flags 是tcp头的标志位 */
__u8 sacked ; /* State flags for SACK/FACK */ #define TCPCB_SACKED_ACKED 0x01 /* SKB ACK'd by a SACK block */
#define TCPCB_SACKED_RETRANS 0x02 /* SKB retransmitted */
#define TCPCB_LOST 0x04 /* SKB is lost */
#define TCPCB_TAGBITS 0x07 /* All tag bits */
#define TCPCB_EVER_RETRANS 0x80 /* Ever retransmitted frame */
#define TCPCB_RETRANS (TCP_SACKED_RETRANS | TCP_EVER_RETRANS) __u32 ack_seq ; /* Sequence number ACK'd */
} #define TCP_SKB_CB(__skb) ((struct tcp_skb_cb *) &((__skb)->cb[0]))

拥塞控制用到的变量:

/* 已发送且还没被ACK的数据包个数 */

packets_out ;

/* 重传且未未被ACK的数据包个数 */

retrans_out ;

/* 被SACKED的数据包个数 */

sacked_out ;

/* 丢失的数据包个数,这是个猜测值 */

loss_out ;

left_out = sacked_out + loss_out ; /* 表示离开网络且未被确认的数据包个数*/

in_flight = packets_out + retrans_out - left_out ; /* 表示处于网络中,还未被确认的数据包个数 */

/* 离开网络且未被确认的数据包个数 */

static inline unsigned int tcp_left_out(const struct tcp_sock *tp)
{
return tp->sacked_out + tp->loss_out ;
}

以下是这些变量的详细介绍:

packets_out is the number of originally transmitted segments above snd.una.

sacked_out is the number of segments acknowledged by SACK blocks.

lost_out is an estimation of the number of segments lost in the network.

retrans_out is the number of retransmitted segments.

Determining the lost_out parameter depends on the selected recovery method.

For example, when FACK is in use, all unacknowledged segments between the highest SACK block

and the cumulative acknowledgement are counted in lost_out.

函数实现

tcp_ack()用于处理接收到的带有ACK标志的段。

/* This routine deals with incoming acks, but not outgoing ones.*/
static int tcp_ack (struct sock *sk, const struct sk_buff *skb, int flag)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
u32 prior_snd_una = tp->snd_una; /* 此ACK之前的snd_una */
u32 ack_seq = TCP_SKB_CB(skb)->seq; /* 此ACK的开始序号 */
u32 ack = TCP_SKB_CB(skb)->ack_seq; /* 此ACK的确认序号 */
u32 prior_in_flight;
u32 prior_fackets;
int prior_packets;
int prior_sacked = tp->sacked_out;
int newly_acked_sacked = 0;
int frto_cwnd = 0; /* If the ack is older than previous acks
* then we can probably ignore it.
* 有负载的TCP段会顺便携带一个ACK序号,即使这个序号已经确认过。
* 如果段中没有SACK选项,就不用处理了。
*/
if (before(ack, prior_snd_una))
goto old_ack; /* If the ack includes data we haven't sent yet, discard
* this segment (RFC793 Section 3.9).
* 我们还没发送的段都已经说确认了,这个ACK是错误的。
*/
if (after(ack, tp->snd_nxt))
goto invalid_ack; /* 此ACK确认了新的数据*/
if (after(ack, prior_snd_una))
flag |= FLAG_SND_UNA_ADVANCED; /* tcp_abc选项处理,累加这个ACK确认的字节数 */
if (sysctl_tcp_abc) {
if (icsk->icsk_ca_state < TCP_CA_CWR)
tp->bytes_acked += ack - prior_snd_una; /* 累积确认了多少字节*/
else if (icsk->icsk_ca_state == TCP_CA_Loss)
/* we assume just one segment left network */
tp->bytes_acked += min(ack-prior_snd_una, tp->mss_cache);
} prior_fackets = tp->fackets_out;
prior_in_flight = tcp_packets_in_flight(tp); /* 如果处于快速路径中*/
if (! (flag & FLAG_SLOWPATH) && after(ack, prior_snd_una)) {
/* Window is constant, pure forward advance.
* No more checks are required.
*/
tcp_update_wl(tp, ack_seq); /*记录更新发送窗口的ACK段序号*/
tp->snd_una = ack; /* 更新发送窗口左端*/
flag |= FLAG_WIN_UPDATE; /* 设置发送窗口更新标志*/ tcp_ca_event(sk, CA_EVENT_FAST_ACK); /* 快速路径拥塞事件钩子*/ NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCHHPACKS); } else { /* 进入慢速路径 */
if (ack_seq != TCP_SKB_CB(skb)->end_seq)
flag |= FLAG_DATA; /* 此ACK携带负荷*/
else /* 纯ACK*/
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPPUREACKS); flag |= tcp_ack_update_window(sk, skb, ack, ack_seq); /* 更新发送窗口*/ /* 根据SACK选项标志重传队列中SKB的记分牌状态*/
if (TCP_SKB_CB(skb)->sacked)
flag |= tcp_sacktag_write_queue(sk, skb, prior_snd_una); /* 查看ACK是否携带ECE标志 */
if (TCP_ECN_rcv_ecn_echo(tp, tcp_hdr(skb)))
flag |= FLAG_ECE; tcp_ca_event(sk, CA_EVENT_SLOW_ACK); /* 慢速路径拥塞事件钩子*/
} /* We pass data and got it acked, remove any soft err log. Something worked...*/
sk->sk_err_soft = 0;
icsk->icsk_probes_out = 0;
tp->rcv_tstamp = tcp_time_stamp; /*ts of last received ACK (for keepalives)*/
prior_packets = tp->packets_out;
if (! prior_packets) /*检查是否有发送且未确认的段*/
goto no_queue; /* 持续定时器处理*/ /* See if we can take anything off the retransmit queue.
* 删除重传队列中已经确认的数据段,并进行时延采样。
*/
flag |= tcp_clean_rtx_queue(sk, prior_fackets, prior_snd_una); /* 经过tcp_clean_rtx_queue之后,
* 计算此次ACKED和SACKED的数据包个数
* /
newly_acked_sacked = (prior_packets - prior_sacked) -
(tp->packets_out - tp->sacked_out) ; /* 处于FRTO处理中,frto_counter的取值为1或2*/
if (tp->frto_counter)
frto_cwnd = tcp_process_frto(sk, flag); /* 判断RTO是否为真的*/
/* Guarantee sacktag reordering detection against wrap-arounds */
if (before(tp->frto_highmark, tp->snd_una))
tp->frto_highmark = 0; /* 如果ACK是重复的、或者带有SACK选项、或者不是Open态*/
if (tcp_ack_is_dubious(sk, flag)) { /* 如果此ACK是可疑的*/
/* 可疑是指已经处于拥塞状态,或刚收到拥塞信号。
* 在这种条件下如果想进行拥塞避免,必须符合:
* 1. 此ACK确认了新的数据
* 2. 不能处于FRTO状态
* 3. 处于Disorder或Loss状态
*/
if ((flag & FLAG_DATA_ACKED) && ! frto_cwnd &&
tcp_may_raise_cwnd(sk, flag))
tcp_cong_avoid(sk, ack, prior_in_flight); /* 拥塞窗口的调节*/ /* 这里进入TCP的拥塞状态机,处理相关拥塞状态*/
tcp_fastretrans_alert(sk, prior_packets - tp->packets_out,
newly_acked_sacked, flag); } else { /* 至少说明ACK在Open态*/
/* ACK不是可疑的,如果ACK确认了新的数据,且不是frto,则进行拥塞避免*/
if ( (flag & FLAG_DATA_ACKED) && ! frto_cwnd )
tcp_cong_avoid(sk, ack, prior_in_flight); /* 拥塞窗口的调节*/
} /* 如果ACK确认了新的段(新的数据段、SYN段、SACK段,或者接收到的ACK不是重复的,
* 则确认该传输控制块的输出路由缓存项是有效的。
*/
if ((flag & FLAG_FORWARD_PROGRESS) || ! (flag & FLAG_NOT_DUP))
dst_confirm(__sk_dst_get(sk));
return 1; no_queue:
/* If this ack opens up a zero window, clear backoff.
* It was being used to time the probes, and is probably far higher than it
* needs to be for normal retransmissions.
*/
if (tcp_send_head(sk)) /* 如果有数据要发送*/
tcp_ack_probe(sk); /* 持续定时器处理*/
return 1; invalid_ack:
SOCK_DEBUG(sk, "ACK %u after %u:%u\n", ack, tp->snd_una, tp->snd_nxt);
return -1; old_ack:
/* 如果此ACK已经确认过,且带有SACK选项的信息*/
if (TCP_SKB_CB(skb)->sacked) {
/* 重新标志各个段的记分牌*/
tcp_sacktag_write_queue(sk, skb, prior_snd_una);
if (icsk->icsk_ca_state == TCP_CA_Open)
tcp_try_keep_open(sk); /* 实际上是看看要不要进入Disorder状态*/
}
/* 会自动打印出调试信息*/
SOCK_DEBUG(sk, "ACK %u before %u:%u\n", ack, tp->snd_una, tp->snd_nxt);
return 0;

old_ack

Q: 什么条件下进入Disorder状态?

A: 如果检测到有被sacked的数据包,或者有重传的数据包,则进入Disorder状态。

当然,之前的状态必须为Open态。

判断条件:sacked_out、lost_out、retrans_out、undo_marker有不为0的。

static void tcp_try_keep_open(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
int state = TCP_CA_Open; /* 如果以下条件成立,那么就从Open进入Disorder状态*/
if (tcp_left_out(tp) || tcp_any_retrans_done(sk) || tp->undo_marker)
state = TCP_CA_Disorder; if (inet_csk(sk)->icsk_ca_state != state) {
tcp_set_ca_state(sk, state);
tp->high_seq = tp->snd_nxt;
}
} #define SOCK_DEBUG(sk, msg...) do { if ((sk) && sock_flag((sk), SOCK_DBG)) \
printk(KERN_DEBUG msg);} while(0)

fast path

记录更新发送窗口的ACK段序号:

static inline void tcp_update_wl(struct tcp_sock *tp, u32 seq)
{
tp->snd_wl1 = seq;
}

ECN

看看是否收到了显示拥塞通知:

static inline int TCP_ECN_rcv_ecn_echo(struct tcp_sock *tp, const struct tcphdr *th)
{
if (th->ece && ! th->syn && (tp->ecn_flags & TCP_ECN_OK))
return 1;
return 0;
}

TCP的核心系列 — ACK的处理(一)的更多相关文章

  1. TCP的核心系列 — ACK的处理(二)

    本文主要内容:tcp_ack()中的一些细节,如发送窗口的更新.持续定时器等. 内核版本:3.2.12 Author:zhangskd @ csdn 发送窗口的更新 什么时候需要更新发送窗口呢? (1 ...

  2. TCP的核心系列 — SACK和DSACK的实现(一)

    TCP的实现中,SACK和DSACK是比较重要的一部分. SACK和DSACK的处理部分由Ilpo Järvinen (ilpo.jarvinen@helsinki.fi) 维护. tcp_ack() ...

  3. TCP的核心系列 — SACK和DSACK的实现(三)

    不论是18版,还是37版,一开始都会从TCP的控制块中取出SACK选项的起始地址. SACK选项的起始地址是保存在tcp_skb_cb结构的sacked项中的,那么这是在什么时候做的呢? SACK块并 ...

  4. TCP的核心系列 — SACK和DSACK的实现(二)

    和18版本相比,37版本的SACK和DSACK的实现做了很多改进,最明显的就是需要遍历的次数少了, 减少了CPU的消耗.37版的性能提升了,代码有大幅度的改动,逻辑也更加复杂了. 本文主要内容:37版 ...

  5. TCP的核心系列 — SACK和DSACK的实现(七)

    我们发送重传包时,重传包也可能丢失,如果没有检查重传包是否丢失的机制,那么只能依靠超时来恢复了. 37版本把检查重传包是否丢失的部分独立出来,这就是tcp_mark_lost_retrans(). 在 ...

  6. TCP的核心系列 — SACK和DSACK的实现(六)

    上篇文章中我们主要说明如何skip到一个SACK块对应的开始段,如何walk这个SACK块包含的段,而没有涉及到 如何标志一个段的记分牌.37版本把给一个段打标志的内容独立出来,这就是tcp_sack ...

  7. TCP的核心系列 — SACK和DSACK的实现(四)

    和18版本不同,37版本把DSACK的检测部分独立出来,可读性更好. 37版本在DSACK的处理中也做了一些优化,对DSACK的两种情况分别进行处理. 本文主要内容:DSACK的检测.DSACK的处理 ...

  8. TCP的核心系列 — 重传队列的更新和时延的采样(二)

    在tcp_clean_rtx_queue()中,并非对每个ACK都进行时延采样.是否进行时延采样,跟这个ACK是否为 重复的ACK.这个ACK是否确认了重传包,以及是否使用时间戳选项都有关系. 本文主 ...

  9. TCP的核心系列 — 重传队列的更新和时延的采样(一)

    重传队列实际上就是发送队列(sk->sk_write_queue),保存着发送且未确认的数据段. 当有新的数据段被确认时,需要把这些段从重传队列中删除,同时更新一些变量,包括 packets_o ...

随机推荐

  1. Scala:输入输出

    http://blog.csdn.net/pipisorry/article/details/52902694 Scala基本输入输出 从屏幕上读取用户输入 有时候我们需要接收用户在屏幕输入的指令来处 ...

  2. PLSQL程序编写杂烦数据表信息编写批量排版

    --PLSQL程序编写杂烦数据表信息编写批量排版 SELECT 'cra.' || lower(t.column_name) ||',' FROM dba_tab_columns t WHERE t. ...

  3. Intellij IDEA 插件开发之自建插件仓库

    Intellij IDEA 有一个自己的官方的插件仓库,但是当我们的开发的 Intellij IDEA 的插件不能够对外公开时,我们就需要搭建自己的 Intellij IDEA 的插件仓库.前不久我们 ...

  4. JAVA面向对象-----内部类的概述

    JAVA面向对象-–内部类的概述s 将类定义在另一个类的内部则成为内部类.其实就是类定义的位置发生了变化. 在一个类中,定义在类中的叫成员变量,定义在函数中的叫成员函数,那么根据类定义的位置也可以分为 ...

  5. UNIX网络编程——Socket通信原理和实践

    我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览器浏览网页时,浏览器的进程怎么与web服务器通信的?当你用QQ聊天时,QQ进程怎么与服务器或你好友所在的QQ进程通信?这些都得靠so ...

  6. Dynamics Crm 2011 Or 2013 IFD 部署一段时间后,CA验证问题

    以下错误描述摘自博客:http://blog.csdn.net/qzw4549689/article/details/14451257 IFD部署一段时间后,大概一年,突然出现从IFD登录页面登录后, ...

  7. CentOs查看文件的几种方式

    有许多命令都可以查看文件,不同的命令有不同的优点,可以针对不同的需要分别选择命令以提高效率:   cat     由第一行开始显示内容,并将所有内容输出   tac     从最后一行倒序显示内容,并 ...

  8. 【一天一道LeetCode】#223. Rectangle Area

    一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Find th ...

  9. parcel和parcelable

    Parcel 在英文中有两个意思,其一是名词,为包裹,小包的意思: 其二为动词,意为打包,扎包.邮寄快递中的包裹也用的是这个词.Android采用这个词来表示封装消息数据.这个是通过IBinder通信 ...

  10. iOS中 简单易懂的秒杀倒计时/倒计时

    示例代码简单易懂: 每日更新关注:http://weibo.com/hanjunqiang  新浪微博 #import <UIKit/UIKit.h> @interface ViewCon ...