Chinaunix

标题: tcp_rcv_synsent_state_process函数的问题【已解决】 [打印本页]

作者: jiufei19    时间: 2014-02-17 22:14
标题: tcp_rcv_synsent_state_process函数的问题【已解决】
本帖最后由 jiufei19 于 2014-02-19 22:47 编辑

linux V2.6.23版本,3次握手tcp客户端收到SYN+ACK后,调用tcp_rcv_synsent_state_process函数进行处理,如下所示

tcp_rcv_synsent_state_process(...)
{
    ...
    if (sk->sk_write_pending ||
              icsk->icsk_accept_queue.rskq_defer_accept ||
              icsk->icsk_ack.pingpong)
{
              /* Save one ACK. Data will be ready after
               * several ticks, if write_pending is set.
               *
               * It may be deleted, but with this feature tcpdumps
               * look so _wonderfully_ clever, that I was not able
               * to stand against the temptation      --ANK
               */      
              inet_csk_schedule_ack(sk);
              icsk->icsk_ack.lrcvtime = tcp_time_stamp;
              icsk->icsk_ack.ato   = TCP_ATO_MIN;
              tcp_incr_quickack(sk);
              tcp_enter_quickack_mode(sk);
              inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,
                            TCP_DELACK_MAX, TCP_RTO_MAX);
             ...
   
}

我的问题是,上述红色代码中if判断的上下文场景是怎样的?或者说什么情况会导致上述if条件成立?
我搜索了代码,发现tcp_rcv_synsent_state_process这个函数只会在3次握手过程中被调用,那么
1、sk->sk_write_pending怎么可能会有不为0的可能呢?
2、rskq_defer_accept这个设置通常是在TCP服务端通过setsocketopt进行设置,那么怎会出现在这里的客户端代码中呢?
3、pingpong模式可以在客户端和服务器端通过setsocketopt进行设置,这个似乎还好理解
4、上述代码中的注释表明了这种写法有利于tcpdumps,这是什么意思?

希望大家帮助,谢谢!
作者: jiufei19    时间: 2014-02-18 10:09
本帖最后由 jiufei19 于 2014-02-18 10:11 编辑

作为TCP的客户端,sk->sk_write_pending不为0的情况是否发生于非阻塞模式?即当connect发出后,客户端直接发出数据,导致调用tcp_sendmsg之类的函数被执行,从而在发送应用数据的同时,将最后一个ACK发给了服务器。

这是我的一个猜想,不知是否能部分解释我的这几个问题?换句话讲,这个问题的实质就是将ACK延迟发送,直到真正有数据发送时才一起捎带发出。
作者: jiufei19    时间: 2014-02-18 10:23
为了验证我刚才说的问题,我查了下代码,如下

   668 int tcp_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
   669         size_t size)
   670 {
   671     struct sock *sk = sock->sk;
   672     struct iovec *iov;
   673     struct tcp_sock *tp = tcp_sk(sk);
   674     struct sk_buff *skb;
   675     int iovlen, flags;
   676     int mss_now, size_goal;
   677     int err, copied;
   678     long timeo;
   679
   680     lock_sock(sk);
   681     TCP_CHECK_TIMER(sk);
   682
   683     flags = msg->msg_flags;
   684     timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);
   685
   686     /* Wait for a connection to finish. */
   687     if ((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))
   688         if ((err = sk_stream_wait_connect(sk, &timeo)) != 0)
   689             goto out_err;

   而
   51 int sk_stream_wait_connect(struct sock *sk, long *timeo_p)
   52 {
   53     struct task_struct *tsk = current;
   54     DEFINE_WAIT(wait);
   55     int done;
   56
   57     do {
   58         int err = sock_error(sk);
   59         if (err)
   60             return err;
   61         if ((1 << sk->sk_state) & ~(TCPF_SYN_SENT | TCPF_SYN_RECV))
   62             return -EPIPE;
   63         if (!*timeo_p)
   64             return -EAGAIN;
   65         if (signal_pending(tsk))
   66             return sock_intr_errno(*timeo_p);
   67
   68         prepare_to_wait(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE);
   69         sk->sk_write_pending++;
              。。。

显然,当第3个ACK没有发回时,客户端将不处于ESTABLISHED状态,于是,将调用红色代码sk_stream_wait_connect,于是在客户端休眠之前,置sk->sk_write_pending为1,这就基本解释了我之前的第1个疑问,我的解释正确吗?

但是第2个问题我还没有找到答案
作者: 瀚海书香    时间: 2014-02-18 13:35
回复 1# jiufei19
1、sk->sk_write_pending怎么可能会有不为0的可能呢?
2、rskq_defer_accept这个设置通常是在TCP服务端通过setsocketopt进行设置,那么怎会出现在这里的客户端代码中呢?
3、pingpong模式可以在客户端和服务器端通过setsocketopt进行设置,这个似乎还好理解


关于第一个,可能会在sk_stream_wait_connect处进行改变吧,没有验证。

关于第二个,存在client端先bind,然后再connect的情况。已经在本地通过systemtap验证

   
作者: 瀚海书香    时间: 2014-02-18 13:57
回复 3# jiufei19
我早上9点打开这个帖子,一直没有回复。回复后才发现已经有人回复了。
第一种情况,跟我猜测的一致。
出现的流程如下:

client 设置socket为nonblock, connect,之后直接调用send

send->tcp_sendmsg->sk_stream_wait_connect->sk_write_pending++.....(waiting)....
                                                                                                              这个时候,ack到来,调用tcp_rcv_synsent_state_process。满足sk_write_pending不为0的情况

   
作者: jiufei19    时间: 2014-02-18 15:00
感谢斑竹的解答,但是我没有明白我的第2个问题斑竹是如何解答的,斑竹说的是:关于第二个,存在client端先bind,然后再connect的情况。已经在本地通过systemtap验证,我没有明白这个解释如何就解释了rskq_defer_accept在客户端被设置的场景?

请斑竹再不吝赐教
作者: 瀚海书香    时间: 2014-02-18 15:40
本帖最后由 瀚海书香 于 2014-02-18 15:40 编辑

回复 6# jiufei19
感谢斑竹的解答,但是我没有明白我的第2个问题斑竹是如何解答的,斑竹说的是:关于第二个,存在client端先bind,然后再connect的情况。已经在本地通过systemtap验证,我没有明白这个解释如何就解释了rskq_defer_accept在客户端被设置的场景?


场景是client端先bind到一个端口和ip,setsockopt(TCP_DEFER_ACCEPT),然后connect server,这个时候就会出现rskq_defer_accept=1的情况。

我在本地通过systemtap,在内核打点的方式验证了这种情况下,当收到server发来的syn/ack的时候,这个时候icsk->icsk->icsk_accept_queue.rskq_defer_accept=1.

client测试代码:
  1. int main(int argc,char **argv)
  2. {
  3.         int listenfd,connfd,opt=1;
  4.         pid_t childpid;
  5.         int i;
  6.         socklen_t clilen;
  7.         struct sockaddr_in servaddr;
  8.         struct sockaddr_in realaddr;
  9.         if (argc != 4) {
  10.                 printf("usage: %s bindip connectip connectport\n", argv[0]);
  11.                 return 0;
  12.         }   
  13.         if ((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1)
  14.                 err_quit("socket error errno=%d", errno);
  15.         bzero(&servaddr,sizeof(servaddr));
  16.         servaddr.sin_family=AF_INET;
  17.         inet_aton(argv[1], &(servaddr.sin_addr));
  18.         servaddr.sin_port = htons(2014);
  19.         opt = 1;
  20.         setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

  21.         opt = 1;
  22.         setsockopt(listenfd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &opt, sizeof(opt));
  23.         if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))==-1)
  24.                 printf("bind error errno=%d\n", errno);

  25.         bzero(&realaddr,sizeof(realaddr));
  26.         realaddr.sin_family=AF_INET;
  27.         realaddr.sin_port=htons(atoi(argv[3]));
  28.         inet_pton(AF_INET,argv[2],&realaddr.sin_addr);
  29.         if (connect(listenfd,(struct sockaddr*)&realaddr,sizeof(realaddr))==-1)
  30.                 printf("connect error errno=%d", errno);
  31.         while(1)
  32.                 sleep(10);
  33. }
复制代码
systemtap验证代码:
  1. %{
  2. #include <net/sock.h>
  3. #include <net/inet_connection_sock.h>
  4. %}

  5. function show_sock:long(skaddr:long)
  6. %{
  7.         struct sock *sk = (struct sock*)(THIS->skaddr);
  8.         struct inet_connection_sock *icsk = inet_csk(sk);
  9.         printk("tcp_defer_accept=%d sk_write_pending=%d\n",
  10.                         icsk->icsk_accept_queue.rskq_defer_accept,
  11.                         sk->sk_write_pending);
  12.         THIS->__retvalue = 1;
  13. %}

  14. probe kernel.function("tcp_rcv_synsent_state_process")
  15. {
  16.         printf("Called function tcp_rcv_synsent_state_process\n")
  17.         show_sock($sk);
  18. }
复制代码

作者: jiufei19    时间: 2014-02-18 16:10
回复 7# 瀚海书香


    哦。斑竹说的这个方式的设置我能理解,我不能理解的是对于TCP客户端,这样设置有何作用,或者说什么样的应用场景才需要这样?
    而对于TCP服务端,这样设置defer模式的好处是显而易见的。关键就是客户端为啥这样做

作者: 瀚海书香    时间: 2014-02-18 16:12
回复 8# jiufei19
哦。斑竹说的这个方式的设置我能理解,我不能理解的是对于TCP客户端,这样设置有何作用,或者说什么样的应用场景才需要这样?
    而对于TCP服务端,这样设置defer模式的好处是显而易见的。关键就是客户端为啥这样做


客户端可以携带data一块ack,减少一次只进行ack的数据交互啊。

   
作者: jiufei19    时间: 2014-02-18 16:23
本帖最后由 jiufei19 于 2014-02-18 16:28 编辑

回复 9# 瀚海书香


    恩,这样做当然可以减少一次纯ACK的发送,但是总觉的有点感觉奇怪。不过这里还有另外一个问题,再请斑竹解惑。

        if (sk->sk_write_pending ||
              icsk->icsk_accept_queue.rskq_defer_accept ||
              icsk->icsk_ack.pingpong) {
              /* Save one ACK. Data will be ready after
               * several ticks, if write_pending is set.
               *
               * It may be deleted, but with this feature tcpdumps
               * look so _wonderfully_ clever, that I was not able
               * to stand against the temptation      --ANK
               */
              inet_csk_schedule_ack(sk);
              icsk->icsk_ack.lrcvtime = tcp_time_stamp;
              icsk->icsk_ack.ato   = TCP_ATO_MIN;
             tcp_incr_quickack(sk);
              tcp_enter_quickack_mode(sk);

              。。。

上面两句红色代码实际是重叠的,即在tcp_enter_quickack_mode这个函数中又会再次执行一遍 tcp_incr_quickack,为啥不直接略掉tcp_incr_quickack,而直接执行后者不就可以了吗,作者为啥偏偏这样写?
作者: 瀚海书香    时间: 2014-02-19 13:34
回复 10# jiufei19
        tcp_incr_quickack(sk);
              tcp_enter_quickack_mode(sk);
              。。。

上面两句红色代码实际是重叠的,即在tcp_enter_quickack_mode这个函数中又会再次执行一遍 tcp_incr_quickack,为啥不直接略掉tcp_incr_quickack,而直接执行后者不就可以了吗,作者为啥偏偏这样写?


看代码比较仔细,赞一个

这个问题在这个commit中修复的:
commit a8cb05b238e0edfd7c63f496d9ce3e238ea68a16
Author: Vijay Subramanian <subramanian.vijay@gmail.com>
Date:   Fri Apr 13 13:23:59 2012 +0000

    tcp: Remove redundant code entering quickack mode
   
    tcp_enter_quickack_mode() already calls tcp_incr_quickack() and sets
    icsk->icsk_ack.ato  to TCP_ATO_MIN. This patch removes the duplication.
   
    Signed-off-by: Vijay Subramanian <subramanian.vijay@gmail.com>
    Reviewed-by: Flavio Leitner <fbl@redhat.com>
    Signed-off-by: David S. Miller <davem@davemloft.net>

diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
index dc1e0be..3dc94fe 100644
--- a/net/ipv4/tcp_input.c
+++ b/net/ipv4/tcp_input.c
@@ -5734,8 +5734,6 @@ static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
                         */
                        inet_csk_schedule_ack(sk);
                        icsk->icsk_ack.lrcvtime = tcp_time_stamp;
-                       icsk->icsk_ack.ato       = TCP_ATO_MIN;
-                       tcp_incr_quickack(sk);
                        tcp_enter_quickack_mode(sk);
                        inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,
                                                  TCP_DELACK_MAX, TCP_RTO_MAX);

   
作者: kkddkkdd11    时间: 2014-02-19 15:44
瀚海书香 发表于 2014-02-18 15:40
回复 6# jiufei19


版主 问个问题:)
这个systemtap脚本 必须32位吗?
我先在 内核版本 3.10

运行 出如下错误:
/tmp/stap51gxb1/stap_dca8c9f67a9d10807625421461d03347_1895_src.c: In function ‘function_show_sock’:
/tmp/stap51gxb1/stap_dca8c9f67a9d10807625421461d03347_1895_src.c:146: error: ‘struct function_show_sock_locals’ has no member named ‘skaddr’
make[1]: *** [/tmp/stap51gxb1/stap_dca8c9f67a9d10807625421461d03347_1895_src.o] Error 1
make: *** [_module_/tmp/stap51gxb1] Error 2
WARNING: kbuild exited with status: 2
Pass 4: compilation failed.  [man error::pass4]

是啥原因?
作者: 瀚海书香    时间: 2014-02-19 18:56
回复 12# kkddkkdd11
版主 问个问题:)
这个systemtap脚本 必须32位吗?
我先在 内核版本 3.10


这个没有限制的。我这边内核是64bit的

   
作者: jiufei19    时间: 2014-02-19 22:46
回复 11# 瀚海书香


    谢谢斑竹帮我解决这个问题,另外我对版主用的啥子systemtap很感兴趣,可以看到内核的变量状态,这个非常有用,我去百度学习下该工具。再次感谢斑竹!
作者: kkddkkdd11    时间: 2014-02-20 10:33
瀚海书香 发表于 2014-02-19 18:56
回复 12# kkddkkdd11


我这边 也能用了
不过 要换成 struct sock *sk = (struct sock*)(STAP_ARG_skaddr);
可能是systemtap版本的问题 :)




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2