当前位置:网站首页>TCP协议之《延迟ACCEPT》
TCP协议之《延迟ACCEPT》
2022-08-10 03:26:00 【程序员扫地僧】
通常情况下,在一个新的TCP连接完成三次握手之后,监听端的accept系统调用就可返回与此对应的子套接口。然而,TCP的延迟ACCEPT功能,允许TCP监听端仅在接收到客户端的数据报文后才去唤醒服务端应用的ACCEPT请求。
一、延迟ACCEPT开启
应用层通过setsockopt系统调用的选项TCP_DEFER_ACCEPT控制延迟ACCEPT功能。用户层下发以秒为单位的数值,内核转换为重传次数值使用,赋值于rskq_defer_accept变量。设置为0秒将关闭此功能。
static int do_tcp_setsockopt(struct sock *sk, int level, int optname, char __user *optval, unsigned int optlen)
{
switch (optname) {
case TCP_DEFER_ACCEPT:
/* Translate value in seconds to number of retransmits */
icsk->icsk_accept_queue.rskq_defer_accept = secs_to_retrans(val, TCP_TIMEOUT_INIT / HZ, TCP_RTO_MAX / HZ);
break;
}
#define TCP_TIMEOUT_INIT ((unsigned)(1*HZ)) /* RFC6298 2.1 initial RTO value */
#define TCP_RTO_MAX ((unsigned)(120*HZ))
转换函数secs_to_retrans的第一个参数为用户层下发的秒值,后两个分别为以秒为单位的TCP初始重传超时时长和最大重传超时时长RTO,参见宏定义两者的值为别为1秒和120秒。
如果用户层下发值大于0的话,最终得到的重传次数值在1到255之间。以重传初始时长timeout开始,每次重传时长递增一倍,知道重传超时的最大值rto_max,之后的重传超时不变使用最大值,知道超时时长大于用户下发值为止,推算出对应的重传次数。
/* Convert seconds to retransmits based on initial and max timeout */
static u8 secs_to_retrans(int seconds, int timeout, int rto_max)
{
if (seconds > 0) {
int period = timeout;
res = 1;
while (seconds > period && res < 255) {
res++;
timeout <<= 1;
if (timeout > rto_max)
timeout = rto_max;
period += timeout;
}
}
return res;
}
在用户层使用系统调用getsockopt的选项TCP_DEFER_ACCEPT获取当前的延迟ACCEPT时长值时,内核使用retrans_to_secs函数将重传次数转换为以秒为单位的时间值,返回给用户层。其实现与以上函数secs_to_retrans的逻辑正相反。
static int do_tcp_getsockopt(struct sock *sk, int level, int optname, char __user *optval, int __user *optlen)
{
switch (optname) {
case TCP_DEFER_ACCEPT:
val = retrans_to_secs(icsk->icsk_accept_queue.rskq_defer_accept, TCP_TIMEOUT_INIT / HZ, TCP_RTO_MAX / HZ);
break;
}
/* Convert retransmits to seconds based on initial and max timeout */
static int retrans_to_secs(u8 retrans, int timeout, int rto_max)
{
if (retrans > 0) {
period = timeout;
while (--retrans) {
timeout <<= 1;
if (timeout > rto_max)
timeout = rto_max;
period += timeout;
}
}
return period;
}
二、服务端接收检查
当处于TCP_NEW_SYN_RECV的请求套接口,接收到对端的报文(通常为ACK)之后,调用函数tcp_check_req进行检查。如果此报文的结束序号end_seq等于请求套接口记录的初始接收序号(rcv_isn)加1,通常rcv_isn为SYN报文使用的序列号,意味着当前报文为无数据的单纯ACK确认报文。并且,超时重传的次数还未超出设定的rskq_defer_accept次数值。内核将请求套接口的acked置位,表明已接收到对端ACK报文,但是并不创建相应的子套接口。
struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb, struct request_sock *req, bool fastopen)
{
/* While TCP_DEFER_ACCEPT is active, drop bare ACK. */
if (req->num_timeout < inet_csk(sk)->icsk_accept_queue.rskq_defer_accept && TCP_SKB_CB(skb)->end_seq == tcp_rsk(req)->rcv_isn + 1) {
inet_rsk(req)->acked = 1;
__NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPDEFERACCEPTDROP);
return NULL;
}
child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL, req, &own_req);
return inet_csk_complete_hashdance(sk, child, req, own_req);
}
三、服务端发送超时
当服务端回复给客户端SYN+ACK报文之后,会启动一个(rsk_timer)超时定时器,以便在超时之后还未能接收到客户端相应的ACK报文,重新发送SYN+ACK报文。由以上的函数tcp_check_req的结束可知,如果延长ACCEPT发挥了作用,内核未能创建子套接口,也没调用到能够停止超时定时器的函数inet_csk_complete_hashdance。所以到时间后,超时处理函数reqsk_timer_handler得到运行。
static void reqsk_timer_handler(struct timer_list *t)
{
struct request_sock *req = from_timer(req, t, rsk_timer);
struct sock *sk_listener = req->rsk_listener;
struct net *net = sock_net(sk_listener);
struct inet_connection_sock *icsk = inet_csk(sk_listener);
struct request_sock_queue *queue = &icsk->icsk_accept_queue;
max_retries = icsk->icsk_syn_retries ? : net->ipv4.sysctl_tcp_synack_retries;
thresh = max_retries;
defer_accept = READ_ONCE(queue->rskq_defer_accept);
if (defer_accept)
max_retries = defer_accept;
syn_ack_recalc(req, thresh, max_retries, defer_accept, &expire, &resend);
if (!expire && (!resend || !inet_rtx_syn_ack(sk_listener, req) || inet_rsk(req)->acked)) {
if (req->num_timeout++ == 0)
atomic_dec(&queue->young);
timeo = min(TCP_TIMEOUT_INIT << req->num_timeout, TCP_RTO_MAX);
mod_timer(&req->rsk_timer, jiffies + timeo);
return;
}
drop:
inet_csk_reqsk_queue_drop_and_put(sk_listener, req);
}
关键判断函数syn_ack_recalc,根据重传次数阈值、延迟ACCEPT次数等参数来确定是否过期以及是否重新发送SYN+ACK报文。如果当前的超时重传次数num_timeout已经大于等于阈值,并且还未接收到对端ACK确认报文,内核认为此连接已失败置位expire。或者即使接收到了ACK报文,但是重传次数已经大于等于最大的重传次数max_retries,内核也认为此连接已失败置位expire变量。
如果为接收到对端的ACK确认报文(acked为零),或者当前的超时重传次数与延迟ACCEPT次数仅相差1次时,置位resend变量重新发送SYN+ACK报文。后一判断条件可为对端建立TCP连接提供保障。
如果expire为真,超时处理函数reqsk_timer_handler将消耗此连接(实际上连接还未真正建立)。否则,如果置位了resend变量,内核使用inet_rtx_syn_ack函数执行SYN+ACK报文的重传。下一次的超时时间设置为初始超时时长TCP_TIMEOUT_INIT左移当前超时次数得到的值,但是不超过最大的超出时长TCP_RTO_MAX。
/* Decide when to expire the request and when to resend SYN-ACK */
static inline void syn_ack_recalc(struct request_sock *req, const int thresh, const int max_retries, const u8 rskq_defer_accept,
int *expire, int *resend)
{
*expire = req->num_timeout >= thresh && (!inet_rsk(req)->acked || req->num_timeout >= max_retries);
/*
* Do not resend while waiting for data after ACK, start to resend on end of deferring period to give
* last chance for data or ACK to create established socket.
*/
*resend = !inet_rsk(req)->acked || req->num_timeout >= rskq_defer_accept - 1;
}
四、客户端检查
TCP客户端接收到服务端回复的SYN+ACK报文之后,进入函数tcp_rcv_synsent_state_process的处理逻辑。如果当前套接口有数据等待发送,或者开启了延迟ACCEPT功能,或者ACK策略设置为pingpong模式,客户端不会立即回复ACK确认报文。对于第一种情况,客户端尝试将数据与ACK一同发送,可减少一次报文发送。但是ACK的延迟时长不能超过TCP_DELACK_MAX定义的时长(200毫秒),如果超时之后,由ACK超时处理函数tcp_delack_timer负责ACK的发送。
static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
if (th->ack) {
if (!th->syn)
goto discard_and_undo;
if (sk->sk_write_pending || icsk->icsk_accept_queue.rskq_defer_accept || icsk->icsk_ack.pingpong) {
inet_csk_schedule_ack(sk);
tcp_enter_quickack_mode(sk);
inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK, TCP_DELACK_MAX, TCP_RTO_MAX);
discard:
tcp_drop(sk, skb);
return 0;
} else {
tcp_send_ack(sk);
}
return -1;
}
}
#define TCP_DELACK_MAX ((unsigned)(HZ/5)) /* maximal time to delay before sending an ACK */
感谢redwingz博主分享优等文章
边栏推荐
猜你喜欢
随机推荐
二维空间下的向量旋转
盘式导电滑环的优点和缺点
一篇文章教你Pytest快速入门和基础讲解,一定要看
Small program subcontracting and subcontracting pre-download
C语言顺序表(源码)
小程序导航及导航传参
charles的功能操作
带你深入理解3.4.2的版本更新,对用户带来了什么?
一文教会你快速上手 Vim
文本编辑器vim
RoyalScope分析仪:发现CAN总线波形台阶和信号幅值低的问题
【每日一题】大佬们进来看看吧
【2022河南萌新联赛第(五)场:信息工程大学】【部分思路题解+代码解析】
搭建Prometheus+Grafana框架监控Hyperledger Fabric的运行
Take you to an in-depth understanding of the version update of 3.4.2, what does it bring to users?
Dijkstra求最短路
如何快速成为一名软件测试工程师?测试员月薪15k需要什么技术?
1413.Minimum Value to Get Positive Step by Step Sum
Did not detect default resource location for test class xxxx
leetcode 27:移除元素









