一、SYN报文处理:
公共部分:tcp_v4_rcv->tcp_v4_do_rcv->tcp_v4_cookie_check(无处理动作)->tcp_rcv_state_process->tcp_v4_conn_request[conn_request]->tcp_conn_request(传入两个参数tcp_request_sock_ops和tcp_request_sock_ipv4_ops,该函数内解析SYN报文的option,req初始化工作)
syncookie连接:inet_reqsk_alloc(创建request_sock)->[tcp_conn_request]tcp_v4_send_synack [send_synack](发送SYNACK)->tcp_make_synack->[tcp_conn_request]reqsk_free(syncookie下不保存request_sock,因此需要在这里进行释放)
TFO连接:inet_reqsk_alloc(创建request_sock)->[tcp_conn_request]tcp_try_fastopen(当不需要syncookie的时候尝试fastopen)->tcp_fastopen_create_child(设置TFO的重传定时器,接收随着SYN报文的数据)->tcp_v4_syn_recv_sock[syn_recv_sock]->tcp_create_openreq_child(创建SYN_RCVD状态的newsk,sock_copy复制listen状态的成员值)->[tcp_v4_syn_recv_sock]inet_ehash_nolisten(把newsk插入到ehash散列表)->[tcp_conn_request]tcp_v4_send_synack [send_synack](发送SYNACK)->tcp_make_synack->[tcp_conn_request]inet_csk_reqsk_queue_add(把req插入到accept队列)
普通连接:inet_reqsk_alloc(创建request_sock)->[tcp_conn_request]inet_csk_reqsk_queue_hash_add->reqsk_queue_hash_req(把req插入ehash散列表中并设置普通连接SYNACK的重传定时器)->inet_ehash_insert->[inet_csk_reqsk_queue_hash_add]inet_csk_reqsk_queue_added(更新半连接逻辑队列qlen的长度)->[tcp_conn_request]tcp_v4_send_synack [send_synack](发送SYNACK)->tcp_make_synack
tcp_conn_request:
1、判断是否使能syncookie,首先需要内核编译启用CONFIG_SYN_COOKIES选项,下面两种场景下会启用syncookie
tcp_syncookies=2
tcp_syncookies=1,半连接逻辑队列满、当前状态不是timewait转换过来的
2、accept队列满,且当前半连接队列有未重传的SYNACK请求,则直接丢弃SYN连接请求报文
3、非timwait切换非syncookie场景下,如果tcp_tw_recycle参数有效,则执行更严格的PAWS校验,如果tcp_tw_recycle无效且参数tcp_syncookies=0,则在半连接队列中预留1/4的空间给TCP metric中已经proved的destination。
二、SYNACK的处理
tcp_v4_rcv->tcp_v4_do_rcv->tcp_rcv_state_process->tcp_rcv_synsent_state_process
tcp_rcv_synsent_state_process:
ACK标志位有效的正常流程:
1、检测收到数据包的ack number、TSopt、RST、SYN等的有效性
2、初始化mtup、fack、mss等
3、调用tcp_finish_connect完成连接
4、如果是TFO,需要进入tcp_rcv_fastopen_synack处理TFO相关内容,如重传未被ACK的数据并重启重传定时器
5、如果写操作被挂起、或者TCP_QUICKACK选项设置为0、或者TCP_DEFER_ACCEPT设置为1,那么设置延迟ACK,正确节省一个ACK报文的消耗。
ACK标志位无效的时候
1、检测RST是否有效
2、进行paws检查
3、如果SYN标志位有效,则切换到TCP_SYN_RECV状态,发送SYNACK进行TCP同开的处理。
tcp_finish_connect:更新状态为TCP_ESTABLISHED
1、初始化metrics
2、触发拥塞控制模块init接口来进行拥塞控制模块的初始化
3、初始化rcvbuf和sndbuf
4、初始化keepalive
5、判断是否启动数据包处理的快速路径
6、通过sock_def_wakeup[sk->sk_state_change]唤醒等待的连接
sock_def_wakeup[sk->sk_state_change]
当sk被添加到prequeue或者backlog的时候最终会被tcp_v4_do_rcv处理,请参考release_sock
三、最后的ACK处理:
syncookie连接:tcp_v4_rcv->tcp_v4_do_rcv->tcp_v4_cookie_check->cookie_v4_check->inet_reqsk_alloc(创建request_sock)->[cookie_v4_check]tcp_get_cookie_sock->tcp_v4_syn_recv_sock[syn_recv_sock]->tcp_create_openreq_child(创建SYN_RCVD状态的newsk,sock_copy复制listen状态的成员值)->[tcp_v4_syn_recv_sock]inet_ehash_nolisten(把newsk插入到ehash队列)->[tcp_v4_do_rcv] tcp_child_process->tcp_rcv_state_process(把newsk从SYN_RCVD切换到ESTABLISHED)->sock_def_readable[parent->sk_data_ready](唤醒sk->sk_wq中的等待进程)
TFO连接:tcp_v4_rcv->tcp_v4_do_rcv(tcp_v4_rcv还可能切换到其他函数)->tcp_rcv_state_process[把TFO的sock从SYN_RCVD切换到ESTABLISHED]->tcp_check_req(仅做报文有效性检查会提前return,不处理defer_accept处理,也不调用tcp_v4_syn_recv_sock)->reqsk_fastopen_remove(从fastopen逻辑队列移除这个TFO连接)
普通连接:tcp_v4_rcv(查找之前创建并插入到ehash的request_sock,状态为NEW_SYN_RCVD)->tcp_check_req(报文有效性检测,如PAWS、系列号是否窗口内、纯SYN包的SYNACK重传处理、defer_accept处理等等)->tcp_v4_syn_recv_sock[syn_recv_sock]->tcp_create_openreq_child(创建SYN_RCVD状态的newsk,sock_copy复制listen状态的成员值)->[tcp_v4_syn_recv_sock]inet_ehash_nolisten(把newsk插入到ehash散列表,并替换出之前插入的req)->[tcp_check_req]inet_csk_complete_hashdance(更新半连接队列的长度qlen=qlen-1,并把req插入到accept队列)->[tcp_v4_rcv]tcp_child_process->tcp_rcv_state_process(把newsk从SYN_RCVD切换到ESTABLISHED)
tcp_v4_syn_recv_sock:
Accept队列满或者不能新建立newsk的时候返回Null,静默丢弃三次握手最后的ACK确认报文。
__inet_inherit_port[tcp_v4_syn_recv_sock]:
listen后建立的新连接不一定和listen的socket端口一致,tproxy场景可能不一致。
tcp_check_req:
接收到PAWS校验失败且系列号不相符的SYN报文时候,普通连接回复RST报文并从半连接队列删除req,TFO会把sock加入TFO RST逻辑队列
收到RST报文的时候,普通连接直接从半连接队列删除req并不回复RST,TFO会把sock加入TFO RST逻辑队列
四个队列:
Accept队列:表示等待用户层accept的连接,队列为icsk_accept_queue sk->sk_ack_backlog
半连接逻辑队列: 表示非TFO、非syncookie下的TCP普通连接,已经接收到SYN报文但是还没接收到SYNACK报文的SOCK数量,实际保存在ehash散列表中,队列长度(&inet_csk(sk)->icsk_accept_queue)->qlen
fastopen逻辑队列:表示接收到SYN报文但是还没有进入ESTABLISHED状态的TFO sock数量,TFO sock存在ehash散列表里面,队列长度(&inet_csk(sk)->icsk_accept_queue)->fastopenq.qlen tcp_fastopen_create_child中增加队列长度 当TFO连接进入ESTABLISHED或者直接进入FIN_WAIT1状态时候在reqsk_fastopen_remove函数中削减队列
fastopen rst队列:&inet_csk(sk)->icsk_accept_queue->fastopenq->{rskq_rst_head,rskq_rst_tail} RST处理的差异需要额外注意一下 存活60s。关于这个RST队列描述如下:
To protect the server, it is important to limit the maximum number of
total pending TFO connection requests, i.e., PendingFastOpenRequests
(Section 4.2). When the limit is exceeded, the server temporarily
disables TFO entirely as described in "Server Cookie Handling"
(Section 4.1.2). Then, subsequent TFO requests will be downgraded to
regular connection requests, i.e., with the data dropped and only
SYNs acknowledged. This allows regular SYN flood defense techniques
[RFC4987] like SYN cookies to kick in and prevent further service
disruption.
The main impact of SYN floods against the standard TCP stack is not
directly from the floods themselves costing TCP processing overhead
or host memory, but rather from the spoofed SYN packets filling up
the often small listener's queue.
On the other hand, TFO SYN floods can cause damage directly if
admitted without limit into the stack. The reset (RST) packets from
the spoofed host will fuel rather than defeat the SYN floods as
compared to the non-TFO case, because the attacker can flood more
SYNs with data and incur more cost in terms of data processing
resources. For this reason, a TFO server needs to monitor the
connections in SYN-RCVD being reset in addition to imposing a
reasonable max queue length. Implementations may combine the two,
e.g., by continuing to account for those connection requests that
have just been reset against the listener's PendingFastOpenRequests
until a timeout period has passed.