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 36 bytes on 32-bit architectures,
* 40 bytes on 64-bit machines, if this grows please adjust
* skbuff.h:skbuff->cb[xxx] size appropriately.
*/
//传输tcp包的控制结构
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 */
__u32 seq; /* Starting sequence number */
__u32 end_seq; /* SEQ + FIN + SYN + datalen */
__u32 when; /* used to compute rtt's */
__u8 flags; /* TCP header flags. */
/* NOTE: These must match up to the flags byte in a
* real TCP header.
*/
#define TCPCB_FLAG_FIN 0x01
#define TCPCB_FLAG_SYN 0x02
#define TCPCB_FLAG_RST 0x04
#define TCPCB_FLAG_PSH 0x08
#define TCPCB_FLAG_ACK 0x10
#define TCPCB_FLAG_URG 0x20
#define TCPCB_FLAG_ECE 0x40
#define TCPCB_FLAG_CWR 0x80
__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 (TCPCB_SACKED_RETRANS|TCPCB_EVER_RETRANS)
__u16 urg_ptr; /* Valid w/URG flags is set. */
__u32 ack_seq; /* Sequence number ACK'd */
};
* tcp协议函数调用关系

* tcp包接收的主函数调用关系
tcp_v4_rcv()
->__inet_lookup() // 查找sock结构
-> xfrm4_policy_check() // 安全策略检查
-> tcp_prequeue() // tcp包预处理
->tcp_v4_do_rcv()
-> tcp_rcv_established() //处理连接完成后的输入包
->tcp_v4_hnd_req() // 处理listen状态
->tcp_rcv_state_process() // 其他状态处理
* tcp包的接收主流程
/*
* From tcp_input.c
*/
int tcp_v4_rcv(struct sk_buff *skb)
{
const struct iphdr *iph;
struct tcphdr *th;
struct sock *sk;
int ret;
struct net *net = dev_net(skb->dev);
// 不是属于自己的包,丢弃。 这里在ip包处理时已经检查过了?
if (skb->pkt_type != PACKET_HOST)
goto discard_it;
/* Count it even if it's bad */
// 对tcp包接收数量,做一个计数
TCP_INC_STATS_BH(net, TCP_MIB_INSEGS);
// 检查tcp头的长度是否正确,tcp的长度至少要是20字节(没有任何选项)
if (!pskb_may_pull(skb, sizeof(struct tcphdr)))
goto discard_it;
// 获取tcp包的头地址
th = tcp_hdr(skb);
// doff是tcp首部长度(大小4bit),表示tcp首部长度时,单位是32bit(4字节),所以总共可以表示(2^4-1)*4个字节\
// 若tcp首部大小小于tcp头结构/4
if (th->doff < sizeof(struct tcphdr) / 4)
goto bad_packet;
if (!pskb_may_pull(skb, th->doff * 4))
goto discard_it;
/* An explanation is required here, I think.
* Packet length and doff are validated by header prediction,
* provided case of th->doff==0 is eliminated.
* So, we defer the checks. */
// 检查tcp的校验和
if (!skb_csum_unnecessary(skb) && tcp_v4_checksum_init(skb))
goto bad_packet;
// 获取tcp头地址
th = tcp_hdr(skb);
// 获取ip头地址
iph = ip_hdr(skb);
// 把tcp头中的seq,序列号字段从网络序,转换成主机序
TCP_SKB_CB(skb)->seq = ntohl(th->seq);
// 该数据包的结束序号是起始序号seq+syn+fin+datalen的值
TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +
skb->len - th->doff * 4);
// 把确认(ack)序号转换成主机序
TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);
// 用于计算rtt值的when变量
TCP_SKB_CB(skb)->when = 0;
// tcp头的标志位设置成ip头的tos的值?why?
TCP_SKB_CB(skb)->flags = iph->tos;
// ack的类型,sack/fack
TCP_SKB_CB(skb)->sacked = 0;
// 在活动socket的hash表中,查找socket结构和sock结构。
// 目的和源的ip地址,以及两端的端口号,网络设备的索引号,和skb->dst->rt_iif变量,进行hash值的计算。
// 若在hash表中找到则,根据socket的状态继续下面的处理,若没有找到则直接调用tcp_v4_send_reset()函数
// 发送RESET分组。
sk = __inet_lookup(net, &tcp_hashinfo, iph->saddr,
th->source, iph->daddr, th->dest, inet_iif(skb));
// 没有找到对应的sk,转到no_tcp_socket段执行
if (!sk)
goto no_tcp_socket;
// 到这里说明已经找到相应的socket结构,开始对不同的状态进行处理。
process:
// 若sock的状态是TIME_WAIT,直接跳到do_time_wait标签处进行处理。
if (sk->sk_state == TCP_TIME_WAIT)
goto do_time_wait;
// 检查xfrm4框架是否有ipsec相关安全策略,若有,且没有通过安全检查,则直接丢弃该分组
if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))
goto discard_and_relse;
// 调用reset??
nf_reset(skb);
// 若该sock的过滤器被激活,则调用sk_filter来检查socket buffer,若返回负数,丢弃数据包。
// 否则继续处理数据包
if (sk_filter(sk, skb))
goto discard_and_relse;
skb->dev = NULL;
// 加锁
bh_lock_sock_nested(sk);
ret = 0;
// 若sk没有被其他用户进程使用,则进行以下处理
// 说明:若socket没有被上半部处理锁定,就尝试把数据包放到预处理队列中,
// 预处理队列是用户空间缓冲区,这样可以加快数据包的传输效率。
if (!sock_owned_by_user(sk)) {
#ifdef CONFIG_NET_DMA
struct tcp_sock *tp = tcp_sk(sk);
if (!tp->ucopy.dma_chan && tp->ucopy.pinned_list)
tp->ucopy.dma_chan = get_softnet_dma();
if (tp->ucopy.dma_chan)
ret = tcp_v4_do_rcv(sk, skb);
else
#endif
{
// 若sk没有被用户进程使用,此时在这里进行处理
// 若tcp_prequeue返回0,表示没有用户和该sock联系,
// 此时tcp_v4_do_rcv被调用,继续进行进行tcp接收的慢路径处理
if (!tcp_prequeue(sk, skb))
ret = tcp_v4_do_rcv(sk, skb);
}
} else // 若用户进程正在使用该sk,则把该sk放入到sk_backlog链表中
sk_add_backlog(sk, skb);
// 解sock锁
bh_unlock_sock(sk);
// 减少sock的引用计数,说明sock已经被释放
sock_put(sk);
return ret;
// 若数据包到这个标签,说明目前还没有属于该数据包的活动socket。
no_tcp_socket:
// 若没有找到tcp的sk,则检查是否有安全策略,若不符合安全策略,直接丢弃
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
goto discard_it;
// 检查该数据包的长度是否合法,检查该数据包的checksum是否合法
if (skb->len < (th->doff << 2) || tcp_checksum_complete(skb)) {
bad_packet:
// 若是一个坏包,只添加引用计数
TCP_INC_STATS_BH(net, TCP_MIB_INERRS);
} else {
// 若数据包本身没有问题,直接发送RESET分组
tcp_v4_send_reset(NULL, skb);
}
// 丢弃该数据包(ip包)
discard_it:
/* Discard frame. */
kfree_skb(skb);
return 0;
discard_and_relse:
// 减少sock的引用计数,并且丢弃该数据包
sock_put(sk);
goto discard_it;
// 当sock处于time_wait状态时,跳到这里进行处理
do_time_wait:
// 安全策略检查(ipsec)
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
inet_twsk_put(inet_twsk(sk));
goto discard_it;
}
// 若ip包的长度太短,校验和不正确,丢弃该数据包
if (skb->len < (th->doff << 2) || tcp_checksum_complete(skb)) {
TCP_INC_STATS_BH(net, TCP_MIB_INERRS);
inet_twsk_put(inet_twsk(sk));
goto discard_it;
}
// 根据到达数据包的类型(syn,fin,数据)等,决定如何处理该包。
// 以下是接收到各种数据包的情况:
// TCP_TW_SUCCESS 0 晚到的数据分组或是重复的ack,此时直接丢弃该数据包
// TCP_TW_RST 1 收到FIN,发送RESET给对方
// TCP_TW_ACK 2 最后的ACK,直接发送ACK到对方
// TCP_TW_SYN 3 收到SYN,尝试重新打开连接
switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {
case TCP_TW_SYN: {
struct sock *sk2 = inet_lookup_listener(dev_net(skb->dev),
&tcp_hashinfo,
iph->daddr, th->dest,
inet_iif(skb));
if (sk2) {
inet_twsk_deschedule(inet_twsk(sk), &tcp_death_row);
inet_twsk_put(inet_twsk(sk));
sk = sk2;
goto process;
}
/* Fall through to ACK */
}
case TCP_TW_ACK:
tcp_v4_timewait_ack(sk, skb);
break;
case TCP_TW_RST:
goto no_tcp_socket;
case TCP_TW_SUCCESS:;
}
goto discard_it;
}
* 快速路径-预处理队列
对于一个tcp包,有两个输入路径:慢速路径和快速路径。
. 慢速路径
慢速路径是一般的tcp包输入处理方式。
每个socket都有两个缓存区队列,一个是接受缓存队列(receive queue ),另一个是(backlog queue),当接受队列满了或是socket正在被占用时,就会把数据包放入到backlog队列中。在慢速路径中,只有在包含有序数据段的检查合法后,才放到tcp的socket接收队列中。这个过程比较复杂,并在接收函数的“后半部分”进行处理。一旦数据包被放到socket的接收队列,socket就被唤醒,此时调度器开始执行用户级的程序将会从该队列中读取数据包。慢速路径处理过程中,当接收队列满了或用户进程的socket被锁定,数据包将会被防到backlog queue队列中。
. 快速路径
为了提高tcp的数据处理效率,使用了快速路径(fast path)。
除了慢速路径的两个处理队列外,linux实现的tcp协议栈还有第三个缓冲队列,称为:预缓存队列(prequeue.)。该队列被快速路径使用。
/* Packet is added to VJ-style prequeue for processing in process
* context, if a reader task is waiting. Apparently, this exciting
* idea (VJ's mail "Re: query about TCP header on tcp-ip" of 07 Sep 93)
* failed somewhere. Latency? Burstiness? Well, at least now we will
* see, why it failed. 8)8) --ANK
*
* NOTE: is this not too big to inline?
*/
// 该函数把tcp数据包放到预处理队列中。
// 若用户的socket被锁定(ucopy.task值非空),当socket被唤醒,且调用read操作时,内核立即处理预处理队列中的数据。
/*
* 该函数实现以下功能:若用户正阻塞在该socket上read,则把当前数据包放入prequeue队列,然后则按以下逻辑处理:
* (1) 若prequeue队列长度大于接收队列长度,则轮训prequeue队列调用tcp_v4_do_rcv进行处理,直到给队列的数据被处理完成,
* (2) 若prequeue队列长度为1,说明只有当前数据包被压入该队列,则唤醒该socket,并开始处理该数据包
*/
/*
* 该函数实现以下功能:若用户正阻塞在该socket上read,则把当前数据包放入prequeue队列,然后则按以下逻辑处理:
* (1) 若prequeue队列长度大于接收队列长度,则轮训prequeue队列调用tcp_v4_do_rcv进行处理,直到给队列的数据被处理完成,
* (2) 若prequeue队列长度为1,说明只有当前数据包被压入该队列,则唤醒该socket,并开始处理该数据包
*/
static inline int tcp_prequeue(struct sock *sk, struct sk_buff *skb)
{
// 获取tcp_sock的指针
struct tcp_sock *tp = tcp_sk(sk);
// 若目前有一个用户正阻塞在该socket上,就会把数据包放到prequeue(预处理队列)中
// 用户可以通过sysctl来控制该系统行为
if (!sysctl_tcp_low_latency && tp->ucopy.task) {
// 把数据包添加到prequeue队列的尾部
__skb_queue_tail(&tp->ucopy.prequeue, skb);
// 更新prequeue队列长度
tp->ucopy.memory += skb->truesize;
// 若目前的prequeue的数据长度大于数据接收队列的长度
if (tp->ucopy.memory > sk->sk_rcvbuf) {
struct sk_buff *skb1;
BUG_ON(sock_owned_by_user(sk));
// 调用backlog_rcv函数处理prequeue中的数据,直到处理完该队列的数据为止
while ((skb1 = __skb_dequeue(&tp->ucopy.prequeue)) != NULL) {
//处理从用户缓冲区中取出的数据包,这里sk_backlog_rcv其实调用的就是tcp_v4_do_rcv函数
sk->sk_backlog_rcv(sk, skb1);
// 统计tcp数据包的个数
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPPREQUEUEDROPPED);
}
// 此时prequeue队列中的数据被处理完成,队列的长度设为为0
tp->ucopy.memory = 0;
} else if (skb_queue_len(&tp->ucopy.prequeue) == 1) {
// 当prequeue队列的长度为1时,唤醒该socket,并开始处理prequeue队列的数据
wake_up_interruptible(sk->sk_sleep);
if (!inet_csk_ack_scheduled(sk))
// 重设ack发送的时间,目的是为了等待数据的到来,和数据一切发送
inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,
(3 * TCP_RTO_MIN) / 4,
TCP_RTO_MAX);
}
// 返回1,表示我们已经把数据包放到prequeue队列中了,
return 1;
}
// 返回0,表示我们没有把数据放到prequeue队列中
// 意味着:调用者的prequeue队列满了, 需要把数据放到backlog接受队列
return 0;
}
* tcp 数据包接收的后半部分处理。
该函数主要进行一下步骤的处理:
. 查看socket的状态,若是TCP_ESTABLISHED,说明tcp连接已经建立起来,此时进行“快速路径”的处理。调用tcp_rcv_established函数,进行处理。
. 是否有完整的tcp头,且完成校验和的计算
. 若是TCP_LISTEN状态,调用tcp_v4_hnd_req函数处理连接请求。
. 最后调用tcp_rcv_state_process函数处理所有阶段的数据包。
/* The socket must have it's spinlock held when we get
* here.
*
* We have a potential double-lock case here, so even when
* doing backlog processing we use the BH locking scheme.
* This is because we cannot sleep with the original spinlock
* held.
*/
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
struct sock *rsk;
#ifdef CONFIG_TCP_MD5SIG
/*
* We really want to reject the packet as early as possible
* if:
* o We're expecting an MD5'd packet and this is no MD5 tcp option
* o There is an MD5 option and we're not expecting one
*/
if (tcp_v4_inbound_md5_hash(sk, skb))
goto discard;
#endif
// 若sock的状态是已经建立成功(TCP_ESTABLISHED),则进入快速路径预处理程序
if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */
TCP_CHECK_TIMER(sk);
if (tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len)) {
rsk = sk;
goto reset;
}
TCP_CHECK_TIMER(sk);
return 0;
}
// 检查tcp头的长度和校验和是否正确
if (skb->len < tcp_hdrlen(skb) || tcp_checksum_complete(skb))
goto csum_err;
// 若socket是listen状态,调用tcp_v4_hnd_req函数处理连接请求
if (sk->sk_state == TCP_LISTEN) {
struct sock *nsk = tcp_v4_hnd_req(sk, skb);
if (!nsk)
goto discard;
if (nsk != sk) {
if (tcp_child_process(sk, nsk, skb)) {
rsk = nsk;
goto reset;
}
return 0;
}
}
// 处理所有其他状态的socket
TCP_CHECK_TIMER(sk);
if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {
rsk = sk;
goto reset;
}
TCP_CHECK_TIMER(sk);
return 0;
// 发送reset数据包
reset:
tcp_v4_send_reset(rsk, skb);
discard:
kfree_skb(skb);
/* Be careful here. If this function gets more complicated and
* gcc suffers from register pressure on the x86, sk (in %ebx)
* might be destroyed here. This current version compiles correctly,
* but you have been warned.
*/
return 0;
csum_err:
TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);
goto discard;
}
* 处理连接状态的socket
连接状态的数据包分为快速路径和慢速路径,都是在tcp_rcv_established函数中完成的。
当tcp三次握手完成后,一个tcp连接就建立起来,此时建立连接的双方就可以开始进行发送数据包进行通信。那么如何保证通信的正确性,如何最大限度的利用连接通道提高发送效率,如何防止发送速度过快导致数据包的丢失,这是该函数需要处理的问题。
该函数的主要目的是尽可能快的把数据复制到上层的用户空间。
* 快速路径预处理
int tcp_rcv_established(struct sock *sk, struct sk_buff *skb,
struct tcphdr *th, unsigned len)
{
struct tcp_sock *tp = tcp_sk(sk);
/*
* Header prediction.
* The code loosely follows the one in the famous
* "30 instruction TCP receive" Van Jacobson mail.
*
* Van's trick is to deposit buffers into socket queue
* on a device interrupt, to call tcp_recv function
* on the receive process context and checksum and copy
* the buffer to user space. smart...
*
* Our current scheme is not silly either but we take the
* extra cost of the net_bh soft interrupt processing...
* We do checksum and copy also but from device to kernel.
*/
tp->rx_opt.saw_tstamp = 0;
/* pred_flags is 0xS?10 << 16 + snd_wnd
* if header_prediction is to be made
* 'S' will always be tp->tcp_header_len >> 2
* '?' will be 0 for the fast path, otherwise pred_flags is 0 to
* turn it off (when there are holes in the receive
* space for instance)
* PSH flag is ignored.
*/
// 设置了PUSH位,所以PUSH位被忽略
// 且序列号是有序的(也就是说socket下一个期望要接受的序列号正好和接收到的包序列号相等)
// 或者说,不是碎片包
if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags &&
TCP_SKB_CB(skb)->seq == tp->rcv_nxt) {
int tcp_header_len = tp->tcp_header_len;
/* Timestamp header prediction: tcp_header_len
* is automatically equal to th->doff*4 due to pred_flags
* match.
*/
/* Check timestamp */
// 检查是否只有时间戳选项,若不只一个时间选项,还有做其他处理
if (tcp_header_len == sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) {
// 存在时间戳选项
__be32 *ptr = (__be32 *)(th + 1);
/* No? Slow path! */
// 还有其他的选项,进入“慢速路径”
if (*ptr != htonl((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16)
| (TCPOPT_TIMESTAMP << 8) | TCPOLEN_TIMESTAMP))
goto slow_path;
// Saw TIMESTAMP on last packet,说明在最新数据包中包含时间戳选项
tp->rx_opt.saw_tstamp = 1;
// 获取时间戳的值
++ptr;
tp->rx_opt.rcv_tsval = ntohl(*ptr);
// 获取echo reply time stamp的值
++ptr;
tp->rx_opt.rcv_tsecr = ntohl(*ptr);
/* If PAWS failed, check it more carefully in slow path */
// 若PAWS选项失败,进入“慢速路径”(若回应时间戳的值小于下一个回应时间戳的值,进入“慢速路径”进一步检查)
// 关于PAWS的概念可以查看
if ((s32)(tp->rx_opt.rcv_tsval - tp->rx_opt.ts_recent) < 0)
goto slow_path;
/* DO NOT update ts_recent here, if checksum fails
* and timestamp was corrupted part, it will result
* in a hung connection since we will drop all
* future packets due to the PAWS test.
*/
}
// 检查tcp头的长度是否太小
if (len <= tcp_header_len) {
/* Bulk data transfer: sender */
// 数据包长度和头部长度相等,说明该数据包只有包头,不包含数据
if (len == tcp_header_len) {
// 若数据长度等于tcp头的长度,说明是一个不带数据的信息包
/* Predicted packet is in window by definition.
* seq == rcv_nxt and rcv_wup <= rcv_nxt.
* Hence, check seq<=rcv_wup reduces to:
*/
// 若期望的数据包在规定的窗口范围内,调用tcp_store_ts_recent()函数进行处理
// ???
if (tcp_header_len ==
(sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
tp->rcv_nxt == tp->rcv_wup)
tcp_store_ts_recent(tp);
/* We know that such packets are checksummed
* on entry.
*/
tcp_ack(sk, skb, 0);
__kfree_skb(skb);
tcp_data_snd_check(sk);
return 0;
} else { /* Header too small */
// tcp的头部太小,丢弃该tcp包
TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);
goto discard;
}
} else {
// 到这里说明数据包的长度大于tcp头长度,也就是带有数据的包,这里开始处理数据
int eaten = 0;
int copied_early = 0;
// 若读取到的序列号和下一次期望接收的序列号相等(可以查看rfc793)
// 且 数据包的长度小于,用户空间缓冲区ucopy的长度ucopy.len
if (tp->copied_seq == tp->rcv_nxt &&
len - tcp_header_len <= tp->ucopy.len) {
#ifdef CONFIG_NET_DMA
if (tcp_dma_try_early_copy(sk, skb, tcp_header_len)) {
copied_early = 1;
eaten = 1;
}
#endif
// 检查目前的socket的进程描述符是否是当前进程
// socket正在被本用户使用,且没有谁copy过该数据包
if (tp->ucopy.task == current &&
sock_owned_by_user(sk) && !copied_early) {
// 设置该进程为RUNNING
__set_current_state(TASK_RUNNING);
// 且把该数据包复制到ucopy缓冲区空间中???
if (!tcp_copy_to_iovec(sk, skb, tcp_header_len))
eaten = 1;
}
if (eaten) {
// 若把已经包数据包复制到用户接收缓冲区中
/* Predicted packet is in window by definition.
* seq == rcv_nxt and rcv_wup <= rcv_nxt.
* Hence, check seq<=rcv_wup reduces to:
*/ ?????
if (tcp_header_len ==
(sizeof(struct tcphdr) +
TCPOLEN_TSTAMP_ALIGNED) &&
tp->rcv_nxt == tp->rcv_wup)
tcp_store_ts_recent(tp);
tcp_rcv_rtt_measure_ts(sk, skb);
__skb_pull(skb, tcp_header_len);
tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITSTOUSER);
}
if (copied_early)
tcp_cleanup_rbuf(sk, skb->len);
}
if (!eaten) {
// 若没有复制到用户缓冲区中,可能是校验和不对
// 这里进行校验和的计算,若成功,查看是否socket的空间足够,若不够,走慢速路径
//
if (tcp_checksum_complete_user(sk, skb))
goto csum_error;
/* Predicted packet is in window by definition.
* seq == rcv_nxt and rcv_wup <= rcv_nxt.
* Hence, check seq<=rcv_wup reduces to:
*/
if (tcp_header_len ==
(sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
tp->rcv_nxt == tp->rcv_wup)
tcp_store_ts_recent(tp);
tcp_rcv_rtt_measure_ts(sk, skb);
// 检查socket的预留空间是否足够
if ((int)skb->truesize > sk->sk_forward_alloc)
goto step5;
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITS);
/* Bulk data transfer: receiver */
// 移除skb中的tcp头的部分
__skb_pull(skb, tcp_header_len);
// 把数据放入到接收队列中
__skb_queue_tail(&sk->sk_receive_queue, skb);
skb_set_owner_r(skb, sk);
// 设置rcv_nxt的值
tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
}
tcp_event_data_recv(sk, skb);
if (TCP_SKB_CB(skb)->ack_seq != tp->snd_una) {
/* Well, only one small jumplet in fast path... */
tcp_ack(sk, skb, FLAG_DATA);
tcp_data_snd_check(sk);
if (!inet_csk_ack_scheduled(sk))
goto no_ack;
}
if (!copied_early || tp->rcv_nxt != tp->rcv_wup)
__tcp_ack_snd_check(sk, 0);
no_ack:
#ifdef CONFIG_NET_DMA
if (copied_early)
__skb_queue_tail(&sk->sk_async_wait_queue, skb);
else
#endif
if (eaten)
__kfree_skb(skb);
else
sk->sk_data_ready(sk, 0);
return 0;
}
}
// 慢速路径处理,到这里说明有一些原因无法处理
slow_path:
if (len < (th->doff << 2) || tcp_checksum_complete_user(sk, skb))
goto csum_error;
/*
* RFC1323: H1. Apply PAWS check first.
*/
// 检查时间戳标记和PAW是否合法,若不合法丢弃该数据包
if (tcp_fast_parse_options(skb, th, tp) && tp->rx_opt.saw_tstamp &&
tcp_paws_discard(sk, skb)) {
if (!th->rst) {
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSESTABREJECTED);
tcp_send_dupack(sk, skb);
goto discard;
}
/* Resets are accepted even if PAWS failed.
ts_recent update must be made after we are sure
that the packet is in window.
*/
}
/*
* Standard slow path.
*/
// 标准慢速路径
// 检查所有输入数据包的序列号
if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {
/* RFC793, page 37: "In all states except SYN-SENT, all reset
* (RST) segments are validated by checking their SEQ-fields."
* And page 69: "If an incoming segment is not acceptable,
* an acknowledgment should be sent in reply (unless the RST bit
* is set, if so drop the segment and return)".
*/
if (!th->rst)
tcp_send_dupack(sk, skb);
goto discard;
}
// 若我们接收到了一个RESET包,丢弃该数据包
if (th->rst) {
tcp_reset(sk);
goto discard;
}
tcp_replace_ts_recent(tp, TCP_SKB_CB(skb)->seq);
if (th->syn && !before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {
TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPABORTONSYN);
tcp_reset(sk);
return 1;
}
step5:
// 第5部处理建立建立状态的数据包
if (th->ack)
tcp_ack(sk, skb, FLAG_SLOWPATH);
tcp_rcv_rtt_measure_ts(sk, skb);
//第6部处理urgent标志
/* Process urgent data. */
tcp_urg(sk, skb, th);
// 第7部处理包的数据部分
/* step 7: process the segment text */
tcp_data_queue(sk, skb);
tcp_data_snd_check(sk);
tcp_ack_snd_check(sk);
return 0;
csum_error:
TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);
discard:
__kfree_skb(skb);
return 0;
}