tcp_rcv_established

2022/01/26 kernel

函数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

  1. 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,表明当前没有未确认的接收报文数据。

  2. tcp_sock{}->ucopy.task就指向了当前进程current,并且调用函数sock_owned_by_user返回非NULL,说明这是tcp_v4_rcv –> tcp_v4_do_rcv –> tcp_rcv_established在当前进程current上下文中调用,并且sock{}结构被应用程序占用。

 

Search

    Table of Contents