注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

网络协议栈(6)RFC793TCP连接时部分异常流程及实现  

2011-12-14 23:19:25|  分类: Linux内核 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
一、一些边界及不和谐情况
一直以为TCP的connect就是通过三次握手来实现的,但是今天看书看到说有同时打开,这个时候是四次握手。正是印证只有想不到,没有做不到的道理。当然这里不是说这是TCP的bug,而是TCP在自己的RFC中预见并讨论了这种情况,其中说明了这种同时打开出现的场景以及TCP应该表现的行为。最后的结论是相当让人镇精的,因为这个连接时可以建立起来的,而且是被TCP协议允许的,虽然这是一种非传统的连接练级方式。
看了一下RFC793《TRANSMISSION CONTROL PROTOCOL》第3.4.  Establishing a connection,里面还描述了另外一些比较冷僻的路径,其中不仅涉及到了同时打开,甚至还有崩溃之后的恢复等问题,看来这个协议还的确是尝试在不可靠的IP层上建立一个可靠的连接,即使崩溃也要有确定、预知的行为,或者说,死也要死个明白。
下面的网址是相关内容的网络上的一个文档http://www.freesoft.org/CIE/Course/Section4/10.htm,当然也可以到http://www.rfc-editor.org/搜索793看完整的TCP规范。
二、同时打开
在这这里有一个比较生动的图片,其中形象的描述了这里的流程 http://www.tcpipguide.com/free/diagrams/tcpopensimul.png。这里可以看到,两个套接口真的是心有灵犀,同时向对方发出连接请求,既然它们是这么的有缘分,那TCP规范就没有理由让这么一对有情人(哦,是有情套接口)不成眷属:
网络协议栈(6)RFC793TCP连接时部分异常流程及实现 - Tsecer - Tsecer的回音岛
TCP RFC的3.4.  Establishing a connection中描述了这个过程,这里和三次握手最为明显的区别和特征就是当发送启动方发送了SYN报文之后,它受到的回应报文里有SYN标志,但是没有ACK标志,这个是和三次握手最为明显的区别,并且是协议栈可以感知的一个严重问题,所以不能套用三次握手的模式,而必须使用专门的代码进行控制。
我们现在就是看一下内核中对于这个连接的处理方法:
tcp_v4_do_rcv--->>>tcp_rcv_state_process--->>>tcp_rcv_synsent_state_process
    if (th->ack) {
        /* rfc793:
……
        return -1;
    }
走到这里,说明是th->ack没有置位,所以就是上面说的特殊情况,可能是同时打开的一个特征,
    if (th->syn) {
        /* We see SYN without ACK. It is attempt of
         * simultaneous connect with crossed SYNs.

         * Particularly, it can be connect to self.
         */
        tcp_set_state(sk, TCP_SYN_RECV);这里最为重要的就是即使没有ack,同样进入TCP_SYN_RECV,只要对方回应了ACK之后,就可以进入ESTABLISHED状态,
……     
   tcp_send_synack(sk);自己本着团结互助的原则,同样给对方发送一个ACK,从而让对方也进入连接状态,从而它们两个就建立了连接。
这里还有神奇的一个特征,它们都不需要侦听,连个都是只要bind之后,不需要执行listen系统调用成为侦听套接口,这真是和谐家庭的典范!
三、重复SYN
没有找图,就用RFC的说明将就点看了。

      TCP A                                                TCP B

  1.  CLOSED                                               LISTEN

  2.  SYN-SENT    --> <SEQ=100><CTL=SYN>               ...

  3.  (duplicate) ... <SEQ=90><CTL=SYN>               --> SYN-RECEIVED

  4.  SYN-SENT    <-- <SEQ=300><ACK=91><CTL=SYN,ACK>  <-- SYN-RECEIVED

  5.  SYN-SENT    --> <SEQ=91><CTL=RST>               --> LISTEN
 

  6.              ... <SEQ=100><CTL=SYN>               --> SYN-RECEIVED

  7.  SYN-SENT    <-- <SEQ=400><ACK=101><CTL=SYN,ACK>  <-- SYN-RECEIVED

  8.  ESTABLISHED --> <SEQ=101><ACK=401><CTL=ACK>      --> ESTABLISHED

                    Recovery from Old Duplicate SYN

                               Figure 9.
 这里看一下TCP A的处理流程,这里TCP A 之前发送的序列号为90的报文在网上迷茫了一段时间,然后竟然又神奇的到达了TCP B。但是在A游荡的过程中,TCP A已经不耐烦了,重新发送了一个同步报文,这次它的序列号比原来的序列号可能要大(如果相等的话,服务器多接受一次相同的同步消息是没有关系的,坏就坏在可能受到不同的seq号的消息)。规范里说,server对这个同步是没有判断能力的,因为server本质工作就是等待SYN连接,进门都是客,也不好就直接拒绝一个连接。但是连接的发送方是知道自己的情况的,因为server的响应(IP+TCPPORT)报文可以唯一确定本机上的一个套接口,这个套接口又是有状态的,所以这个client套接口就可以知道服务器是不是受到了重复的早期同步报文。这里一点是,TCP的server(listen状态的server socket)总是对第一个SYNC做出正确的反应,即在请求seq的基础上加一作为ack_seq,然后给出自己的seq,并在回应报文中置位SYN和ACK标志。好了,服务器到时利索了,那就看客户端了。客户端的实现代码为:
tcp_v4_do_rcv--->>tcp_rcv_state_process--->>>tcp_rcv_synsent_state_process
if (th->ack) {
        /* rfc793:
         * "If the state is SYN-SENT then
         *    first check the ACK bit
         *      If the ACK bit is set
         *      If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send
         *        a reset (unless the RST bit is set, if so drop
         *        the segment and return)"
         *
         *  We do not send data with SYN, so that RFC-correct
         *  test reduces to:
         */
        if (TCP_SKB_CB(skb)->ack_seq != tp->snd_nxt) 对于上面的例子,这里的TCP_SKB_CB(skb)->ack_seq为91,而tp->snd_nxt则为101,即上面第二行中的101报文的确认号。因为确认中的90报文是半路杀出的程咬金,所以客户端是不承认(或者感知不到)这个SYN的
            goto reset_and_undo;
……
reset_and_undo:
    tcp_clear_options(&tp->rx_opt);
    tp->rx_opt.mss_clamp = saved_clamp;
    return 1;

然后返回到tcp_v4_do_rcv函数中
    if (tcp_rcv_state_process(sk, skb, skb->h.th, skb->len)) {这里返回值为1,所以reset
        rsk = sk;
        goto reset;
    }
……
reset:
    tcp_v4_send_reset(rsk, skb);这里就发送了reset,对方要倒霉了,这属于单方面反悔行为。
四、一方猝死之后的再连接
这种情况发生在两个套接口是正在友好连接的时候,之后由于系统崩溃,此时整个系统信息丢失。其实也没有必要系统崩溃,只要这个进程被内核强制关闭就好了(未确认)。就好像电视剧里演绎的,突然间来了一个大的动荡,从此那人儿杳无音讯,然后过一段时间,他/她又突然出现,此时他们的再次接触会擦出什么样的火花?

      TCP A                                           TCP B

  1.  (CRASH)                               (send 300,receive 100)

  2.  CLOSED                                           ESTABLISHED

  3.  SYN-SENT --> <SEQ=400><CTL=SYN>              --> (??)

  4.  (!!)     <-- <SEQ=300><ACK=100><CTL=ACK>     <-- ESTABLISHED

  5.  SYN-SENT --> <SEQ=100><CTL=RST>              --> (Abort!!)

  6.  SYN-SENT                                         CLOSED

  7.  SYN-SENT --> <SEQ=400><CTL=SYN>              -->

                     Half-Open Connection Discovery

                               Figure 10.
此时的状态是对方还处于甜蜜的连接状态建立态,而此时的TCP A经过一次涅槃之后再次来连接TCP B(我们可以认为这货失意了)。这里就造成TCP B就很困惑,在第三行可以看到 TCP 的状态为 “??”(神马情况??)。此时由于连接已经建立,所以TCP B 变得比较固执,它认为TCP A此时只是一个恶意的玩笑,所以它再次强调一下,自己希望收到的报文序列号是100(即第四行的ACK=100,并且自己的当前序列号为300),希望对方别逗了。
此时轮到TCP A大吃一惊了"!!",所以一不做二不休,直接复位一下连接吧,所以发送了RST。虽然这是一封绝交信,但是基本的礼仪还是要遵守的,比方说它依然是使用了对方希望的序列号100,虽然它之前对这个序列号一无所知(因为它是是刚创建的套接口,并且它自主选择的序列号为第三行中体现的400,此时为了弄死对方,它违心的选择了对方ack中要求的序列号100)。
当这个包含着RST标志的报文到达对方之后,对方只能就范,被推倒了,直接abort。
这里TCP A 发送RST的流程其实是和第三节中描述的路径相同的,同样是不满足
  if (th->ack) {
        /* rfc793:
         * "If the state is SYN-SENT then
         *    first check the ACK bit
         *      If the ACK bit is set
         *      If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send
         *        a reset (unless the RST bit is set, if so drop
         *        the segment and return)"
         *
         *  We do not send data with SYN, so that RFC-correct
         *  test reduces to:
         */
        if (TCP_SKB_CB(skb)->ack_seq != tp->snd_nxt) 这里TCP_SKB_CB(skb)->ack_seq100,而tp->snd_nxt为401
            goto reset_and_undo;
五、TCP_SKB_CB(skb)->ack_seq 的初始化
位于函数
int tcp_v4_rcv(struct sk_buff *skb):

    TCP_SKB_CB(skb)->seq = ntohl(th->seq);
    TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +
                    skb->len - th->doff * 4);
    TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);即是和TCP头中的名字相同并一一对应的。
……
        {
            if (!tcp_prequeue(sk, skb))
            ret = tcp_v4_do_rcv(sk, skb);
        }
  评论这张
 
阅读(1369)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017