函数tcp_rcv_established用于处理TCP_ESTABLISHED状态下的接收报文,处理过程分为fastpath和slowpath。
1、Code Analysis
tcp_rcv_established
│
├──如果接收报文TCP header的第四个32bits数,掩去4bits保留位和TCP flag PSH后的当前值,
│ 恰好等于tcp_sock{}->pred_flags,并且
│ 接收报文起始序列号tcp_skb_cb{}->seq等于tcp_sock{}->rcv_nxt,i.e. 非乱序报文
│ (fastpath的情况)
│ │
│ ├──如果根据TCP header长度tcp_sock{}->tcp_header_len判断,启用了TCP Timestamps Option
│ │ │
│ │ ├──如果TCP Header位置之后并不是TCP Timestamps Option,跳转至Label slow_path处执行
│ │ │
│ │ ├──tcp_sock{}->rx_opt.saw_tstamp设置为1,表示接收报文携带了TCP Timestamps Option
│ │ │
│ │ ├──从报文中获取sender timestamp value(tcp_sock{}->rx_opt.rcv_tsval)和
│ │ │ echo reply timestamp value(tcp_sock{}->rx_opt.rcv_tsecr)
│ │ │
│ │ └──如果时间戳tcp_sock{}->rx_opt.rcv_tsval早于了
│ │ tcp_sock{}->rx_opt.ts_recent,跳转至Label slow_path处执行
│ │
│ ├──如果接收报文TCP总长度(包括Header和Payload) <= TCP header长度tcp_sock{}->tcp_header_len
│ │ │
│ │ ├──如果接收报文TCP Payload长度为0
│ │ │ │
│ │ │ ├──如果根据TCP header长度tcp_sock{}->tcp_header_len判断,启用了TCP Timestamps Option,
│ │ │ │ 并且tcp_sock{}->rcv_nxt等于tcp_sock{}->rcv_wup,i.e. 接收数据全部已确认
│ │ │ │ (详见Note. 1)
│ │ │ │ │
│ │ │ │ └──tcp_store_ts_recent
│ │ │ │ │
│ │ │ │ ├──使用报文中获取的sender timestamp value(tcp_sock{}->rx_opt.rcv_tsval),
│ │ │ │ │ 来更新tcp_sock{}->rx_opt.ts_recent
│ │ │ │ │
│ │ │ │ └──tcp_sock{}->rx_opt.ts_recent_stamp更新为当前时间
│ │ │ │
│ │ │ ├──tcp_ack:这里输入参数flag赋值为0
│ │ │ │
│ │ │ ├──__kfree_skb释放skb
│ │ │ │
│ │ │ ├──tcp_data_snd_check
│ │ │ │
│ │ │ └──函数直接返回0
│ │ │
│ │ └──否则,接收报文TCP总长度(包括Header和Payload)比预期的TCP header长度还要小
│ │ │
│ │ └──__kfree_skb释放skb,函数最终返回0
│ │
│ └──否则,接收报文带有TCP Payload
│ │
│ ├──局部变量eaten和copied_early都初始化为0
│ │
│ ├──如果tcp_sock{}->copied_seq等于tcp_sock{}->rcv_nxt,i.e. 期望拷贝到用户空间的
│ │ 数据起始序列号与期望接收的报文数据起始序列号一致,并且
│ │ 接收报文TCP Payload长度 <= tcp_sock{}->ucopy.len,i.e. 用户空间待读取的长度能
│ │ 完全容纳接收报文TCP Payload
│ │ │
│ │ ├──如果tcp_sock{}->ucopy.task就指向了当前进程current,并且
│ │ │ 调用函数sock_owned_by_user返回非NULL,i.e. 当前进程有正在使用这个socket,并且
│ │ │ 局部变量copied_early当前值为0
│ │ │ (详见Note. 2)
│ │ │ │
│ │ │ ├──__set_current_state:这里将当前进程current的状态设置为TASK_RUNNING
│ │ │ │
│ │ │ ├──tcp_copy_to_iovec
│ │ │ │ │
│ │ │ │ ├──如果sk_buff{}->ip_summed当前值等于CHECKSUM_UNNECESSARY,
│ │ │ │ │ i.e. 不需要再计算checksum
│ │ │ │ │ │
│ │ │ │ │ └──skb_copy_datagram_iovec:拷贝报文数据到iovec{}结构
│ │ │ │ │ 所指定的用户空间中
│ │ │ │ │
│ │ │ │ ├──否则,仍然需要计算checksum
│ │ │ │ │ │
│ │ │ │ │ └──skb_copy_and_csum_datagram_iovec:拷贝报文数据到iovec{}结构
│ │ │ │ │ 所指定的用户空间中,并校验checksum
│ │ │ │ │
│ │ │ │ └──如果拷贝数据/校验checksum成功
│ │ │ │ │
│ │ │ │ ├──tcp_sock{}->ucopy.len扣除已成功拷贝的数据长度chunk
│ │ │ │ │
│ │ │ │ ├──tcp_sock{}->copied_seq计数累加已成功拷贝的数据长度chunk
│ │ │ │ │
│ │ │ │ └──tcp_rcv_space_adjust
│ │ │ │
│ │ │ └──如果调用函数tcp_copy_to_iovec,成功拷贝接收报文数据到用户空间,
│ │ │ 局部变量eaten设置为1
│ │ │
│ │ └──如果局部变量eaten当前值为1,i.e. 成功拷贝接收报文数据到用户空间
│ │ │
│ │ ├──如果根据TCP header长度tcp_sock{}->tcp_header_len判断,启用了TCP Timestamps Option,
│ │ │ 并且tcp_sock{}->rcv_nxt等于tcp_sock{}->rcv_wup,i.e. 接收数据全部已确认
│ │ │ │
│ │ │ └──tcp_store_ts_recent
│ │ │
│ │ ├──tcp_rcv_rtt_measure_ts
│ │ │ │
│ │ │ └──如果接收报文中的echo reply timestamp value(tcp_sock{}->rx_opt.rcv_tsecr)有非零值,并且
│ │ │ 接收报文TCP Payload长度 >= inet_connection_sock{}->icsk_ack.rcv_mss
│ │ │ │
│ │ │ └──tcp_rcv_rtt_update
│ │ │ (这里输入参数sample赋值为tcp_time_stamp - tcp_sock{}->rx_opt.rcv_tsecr,
│ │ │ 输入参数win_dep赋值为0)
│ │ │
│ │ ├──__skb_pull:剥离TCP Header(包括标准TCP Header和TCP TImestamps Option)
│ │ │
│ │ └──tcp_sock{}->rcv_nxt更新为接收报文的结束序列号tcp_skb_cb{}->end_seq
│ │
│ ├──如果局部变量eaten当前值为0,i.e. 未能成功拷贝接收报文数据到用户空间
│ │ │
│ │ ├──tcp_checksum_complete_user
│ │ │ │
│ │ │ ├──__tcp_checksum_complete_user
│ │ │ │ │
│ │ │ │ └──__tcp_checksum_complete
│ │ │ │ │
│ │ │ │ └──__skb_checksum_complete:校验checksum
│ │ │ │
│ │ │ └──如果sk_buff{}->ip_summed不等于CHECKSUM_UNNECESSARY,并且
│ │ │ 校验checksum失败,函数返回1;否则,函数返回0
│ │ │
│ │ ├──如果校验checksum失败,__kfree_skb释放skb,函数最终返回0
│ │ │
│ │ ├──tcp_rcv_rtt_measure_ts
│ │ │
│ │ ├──如果sk_buff{}->truesize > sock{}->sk_forward_alloc,i.e. 接收缓存不足,
│ │ │ 跳转至Label step5处执行
│ │ │
│ │ ├──__skb_pull:剥离TCP Header(包括标准TCP Header和TCP TImestamps Option)
│ │ │
│ │ ├──__skb_queue_tail:将接收报文挂载到收包队列sock{}->sk_receive_queue的末尾
│ │ │
│ │ ├──sk_stream_set_owner_r
│ │ │
│ │ └──tcp_sock{}->rcv_nxt更新为接收报文的结束序列号tcp_skb_cb{}->end_seq
│ │
│ ├──tcp_event_data_recv
│ │
│ ├──如果接收报文的确认序列号tcp_skb_cb{}->ack_seq不等于已经发送但尚未被确认的第一个数据
│ │ 字节的序列号tcp_sock{}->snd_una,i.e. 对之前所发送数据的正常确认
│ │ │
│ │ ├──tcp_ack:这里输入参数flag赋值为FLAG_DATA
│ │ │
│ │ ├──tcp_data_snd_check
│ │ │
│ │ └──如果inet_connection_sock{}->icsk_ack.pending没有设置ICSK_ACK_SCHED标志,
│ │ 跳转至Label no_ack处执行
│ │
│ ├──__tcp_ack_snd_check:这里输入参数ofo_possible被赋值为0,i.e. 不考虑乱序报文的情况
│ │
│ ├──Label no_ack
│ │
│ ├──如果局部变量eaten当前值为1,i.e. 成功拷贝接收报文数据到用户空间,
│ │ __kfree_skb释放skb
│ │
│ ├──否则,未能成功拷贝接收报文数据到用户空间
│ │ │
│ │ └──执行sock{}->sk_data_ready所指的函数,唤醒等待当前sock{}结构的进程
│ │ (对于IPv4,实际会执行函数sock_def_readable)
│ │
│ └──带TCP Payload的非乱序报文处理完成,函数最终返回0
│
├──Label slow_path
│
├──如果是报文长度错误,或者校验checksum失败,__kfree_skb释放skb,函数最终返回0
│
├──如果调用函数tcp_fast_parse_options能够解析出TCP Option,并且
│ 当前接收报文携带了TCP Timestamps Option,并且
│ 调用函数tcp_paws_discard确认接收报文没有通过PAWS检查
│ │
│ └──如果接收报文TCP flags不带有RST标志
│ │
│ ├──tcp_send_dupack:发送携带DSACK的ACK报文
│ │
│ └──__kfree_skb释放当前报文skb,函数返回0
│
├──如果调用函数tcp_sequence确认接收报文序列号是无效的
│ │
│ └──如果接收报文TCP flags不带有RST标志
│ │
│ ├──tcp_send_dupack:发送携带DSACK的ACK报文
│ │
│ └──__kfree_skb释放当前报文skb,函数返回0
│
├──如果接收报文TCP flags带有RST标志
│ │
│ ├──tcp_reset
│ │
│ └──__kfree_skb释放当前报文skb,函数返回0
│
├──tcp_replace_ts_recent
│
├──如果接收报文TCP flags带有SYN标志,并且
│ 接收报文起始序列号tcp_skb_cb{}->seq不早于tcp_sock{}->rcv_nxt,
│ i.e. 重复SYN报文的序列号是无效的
│ (非TCP_LISTEN或者TCP_SYN_SENT状态下,所接收的SYN报文序列号应该已经被确认了)
│ │
│ ├──tcp_reset
│ │
│ └──函数直接返回1
│
├──Label step5
│
├──如果接收报文TCP flags带有ACK标志
│ │
│ └──tcp_ack:这里输入参数flag赋值为FLAG_SLOWPATH
│
├──tcp_rcv_rtt_measure_ts
│
├──tcp_urg:处理TCP Urgent数据
│
├──tcp_data_queue:在Slowpath中处理TCP Segments
│
├──tcp_data_snd_check:检查是否有数据可以发送
│
├──tcp_ack_snd_check:检查是否需要发送ACK报文
│
└──函数最终返回0
2、Note
-
TCP接收带负载的数据包时,tcp_sock{}->rcv_nxt会被更新;TCP调用函数tcp_transmit_skb发送报文时,在函数tcp_select_window中会使用tcp_sock{}->rcv_nxt去更新tcp_sock{}->rcv_wup。 如果当前tcp_sock{}->rcv_nxt等于tcp_sock{}->rcv_wup,表明当前没有未确认的接收报文数据。
-
tcp_sock{}->ucopy.task就指向了当前进程current,并且调用函数sock_owned_by_user返回非NULL,说明这是tcp_v4_rcv –> tcp_v4_do_rcv –> tcp_rcv_established在当前进程current上下文中调用,并且sock{}结构被应用程序占用。