ICSK_ACK_NOW one-time immediate ACK Quick ack

 今天看tcp ip kernel的时候发现了一个ICSK_ACK_NOW 标志为,分析一下缘由

 

  每次调用 tcp_enter_quickack_mode 时,都会重置与交互会话相关的状态,这会影响对实际交互会话的跟踪和检测。

 In several cases in the TCP code we want to force an immediate ACK,but do not want to call tcp_enter_quickack_mode() because we do not want to forget the icsk_ack.pingpong or icsk_ack.ato state.--->tcp_enter_quickack_mode 函数的原始设计是为了优化交互式会话;

  但在某些情况下,协议本身的要求需要立即发送 ACK。这种情况下,调用 tcp_enter_quickack_mode 会引起不必要的交互会话状态重置,导致潜在的问题。解决方法是将立即发送 ACK 的功能与交互会话检测分离,创建一个新的函数来处理非交互会话的立即 ACK 需求,从而避免混淆和不必要的状态重置。

  • ato 代表 "ACK timeout",即 ACK 超时时间。这个字段用于管理 TCP 协议中延迟确认(delayed ACK)机制的超时时间。
  • icsk_ack.pingpong 的作用pingpong 字段指示当前连接是否处于一种“乒乓”模式。在这种模式下,通信双方会以一种交替的方式发送数据和接收确认。这通常发生在交互式应用程序(如终端会话、远程登录等)中,每次通信的一方发送少量数据后,另一方立即确认并发送回响应数据。
/* ACK is pending.
         * ACK的发送状态标志,可以表示四种情况:
         * 1. ICSK_ACK_SCHED:目前有ACK需要发送
         * 2. ICSK_ACK_TIMER:延迟确认定时器已经启动
         * 3. ICSK_ACK_PUSHED:如果处于快速确认模式,允许立即发送ACK
         * 4. ICSK_ACK_PUSHED2:无论是否处于快速确认模式,都可以立即发送ACK
         
        __u8          pending;     /* ACK is pending
        
        /* 值为1时,为延迟确认模式;值为0时,为快速确认模式。
         * 注意这个标志是不是永久性的,而是动态变更的
        __u8          pingpong;     /* The session is interactive    

参考文章quick ack1   qack2

quick ack效果可以看https://medium.com/fcamels-notes/linux-%E4%B8%8A-tcp-quickack-%E7%9A%84%E6%95%88%E6%9E%9C-86521d5f1190

static void tcp_ecn_accept_cwr(struct sock *sk, const struct sk_buff *skb)
{
	if (tcp_hdr(skb)->cwr) {
		tcp_sk(sk)->ecn_flags &= ~TCP_ECN_DEMAND_CWR;

		/* If the sender is telling us it has entered CWR, then its
		 * cwnd may be very low (even just 1 packet), so we should ACK
		 * immediately.
		 */
		if (TCP_SKB_CB(skb)->seq != TCP_SKB_CB(skb)->end_seq)
			inet_csk(sk)->icsk_ack.pending |= ICSK_ACK_NOW;
	}
}
/*当间隙被部分填充时,__tcp_ack_snd_check 已经检查了
无序队列并正确地发送了立即 ACK。但是,
当间隙被完全填充时,以前的实现只会重置
乒乓模式,这不能保证立即 ACK,因为
快速 ACK 计数器可能为零。此补丁通过
标记一次性立即 ACK 标志来解决此问题。*/

if (!RB_EMPTY_ROOT(&tp->out_of_order_queue)) {
			tcp_ofo_queue(sk);

			/* RFC5681. 4.2. SHOULD send immediate ACK, when
			 * gap in queue is filled.
			 */
			if (RB_EMPTY_ROOT(&tp->out_of_order_queue))
				inet_csk(sk)->icsk_ack.pending |= ICSK_ACK_NOW;
		}

 

什么时候发送?

/* Send ACKs quickly, if "quick" count is not exhausted
 * and the session is not interactive.
 */

static bool tcp_in_quickack_mode(struct sock *sk)
{
    const struct inet_connection_sock *icsk = inet_csk(sk);
    const struct dst_entry *dst = __sk_dst_get(sk);
//如果快速确认模式中可以发送的ACK数量不为0,且设置了快速确认标志 且 dst 路由存在
    return (dst && dst_metric(dst, RTAX_QUICKACK)) ||
        (icsk->icsk_ack.quick && !icsk->icsk_ack.pingpong);
}
    /* More than one full frame received... */
	if (((tp->rcv_nxt - tp->rcv_wup) > inet_csk(sk)->icsk_ack.rcv_mss &&
	     /* ... and right edge of window advances far enough.
	      * (tcp_recvmsg() will send ACK otherwise).
	      * If application uses SO_RCVLOWAT, we want send ack now if
	      * we have not received enough bytes to satisfy the condition.
	      */
	    (tp->rcv_nxt - tp->copied_seq < sk->sk_rcvlowat ||
	     __tcp_select_window(sk) >= tp->rcv_wnd)) ||
	    /* We ACK each frame or... */
	    tcp_in_quickack_mode(sk) ||
	    /* Protocol state mandates a one-time immediate ACK */
	    inet_csk(sk)->icsk_ack.pending & ICSK_ACK_NOW) {
send_now:
		tcp_send_ack(sk);
		return;
	}

 

Previously commit 9aee40006190 ("tcp: ack immediately when a cwr
packet arrives") calls tcp_enter_quickack_mode to force sending
two immediate ACKs upon receiving a packet w/ CWR flag. The side
effect is it'll also reset the delayed ACK timer and interactive
session tracking. This patch removes that side effect by using the
new ACK_NOW flag to force an immmediate ACK.

Packetdrill to demonstrate:

    0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
   +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
   +0 setsockopt(3, SOL_TCP, TCP_CONGESTION, "dctcp", 5) = 0
   +0 bind(3, ..., ...) = 0
   +0 listen(3, 1) = 0

   +0 < [ect0] SEW 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
   +0 > SE. 0:0(0) ack 1 <mss 1460,nop,nop,sackOK,nop,wscale 8>
  +.1 < [ect0] . 1:1(0) ack 1 win 257
   +0 accept(3, ..., ...) = 4

   +0 < [ect0] . 1:1001(1000) ack 1 win 257
   +0 > [ect01] . 1:1(0) ack 1001

   +0 write(4, ..., 1) = 1
   +0 > [ect01] P. 1:2(1) ack 1001

   +0 < [ect0] . 1001:2001(1000) ack 2 win 257
   +0 write(4, ..., 1) = 1
   +0 > [ect01] P. 2:3(1) ack 2001

   +0 < [ect0] . 2001:3001(1000) ack 3 win 257
   +0 < [ect0] . 3001:4001(1000) ack 3 win 257
   // Ack delayed ...

   +.01 < [ce] P. 4001:4501(500) ack 3 win 257
   +0 > [ect01] . 3:3(0) ack 4001
   +0 > [ect01] E. 3:3(0) ack 4501

+.001 read(4, ..., 4500) = 4500
   +0 write(4, ..., 1) = 1
   +0 > [ect01] PE. 3:4(1) ack 4501 win 100

 +.01 < [ect0] W. 4501:5501(1000) ack 4 win 257
   // No delayed ACK on CWR flag
   +0 > [ect01] . 4:4(0) ack 5501

 +.31 < [ect0] . 5501:6501(1000) ack 4 win 257
   +0 > [ect01] . 4:4(0) ack 6501


Fixes: 9aee40006190 ("tcp: ack immediately when a cwr packet arrives")
Signed-off-by: Yuchung Cheng <ycheng@google.com>
Signed-off-by: Neal Cardwell <ncardwell@google.com>
---
 net/ipv4/tcp_input.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
index 9a09ff3afef2..4c2dd9f863f7 100644
--- a/net/ipv4/tcp_input.c
+++ b/net/ipv4/tcp_input.c
@@ -245,16 +245,16 @@ static void tcp_ecn_queue_cwr(struct tcp_sock *tp)
 		tp->ecn_flags |= TCP_ECN_QUEUE_CWR;
 }
 
-static void tcp_ecn_accept_cwr(struct tcp_sock *tp, const struct sk_buff *skb)
+static void tcp_ecn_accept_cwr(struct sock *sk, const struct sk_buff *skb)
 {
 	if (tcp_hdr(skb)->cwr) {
-		tp->ecn_flags &= ~TCP_ECN_DEMAND_CWR;
+		tcp_sk(sk)->ecn_flags &= ~TCP_ECN_DEMAND_CWR;
 
 		/* If the sender is telling us it has entered CWR, then its
 		 * cwnd may be very low (even just 1 packet), so we should ACK
 		 * immediately.
 		 */
-		tcp_enter_quickack_mode((struct sock *)tp, 2);
+		inet_csk(sk)->icsk_ack.pending |= ICSK_ACK_NOW;
 	}
 }
 
@@ -4703,7 +4703,7 @@ static void tcp_data_queue(struct sock *sk, struct sk_buff *skb)
 	skb_dst_drop(skb);
 	__skb_pull(skb, tcp_hdr(skb)->doff * 4);
 
-	tcp_ecn_accept_cwr(tp, skb);
+	tcp_ecn_accept_cwr(sk, skb);
 
 	tp->rx_opt.dsack = 0;
 

 

posted @   codestacklinuxer  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
历史上的今天:
2020-05-31 都知道的copy_from_user
点击右上角即可分享
微信分享提示