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


    一、一些边界及不和谐情况
    一直以为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);
            }

  • 相关阅读:
    [Java123] 方法重载中遇到的编译器错误: both methods have same erasure => 引入Java泛型type erasure
    [Java123] 认识JavaBean
    [Java123] HashMap实现和应用
    [Java123] 开源工具guava比较器链ComparisonChain (附加比较null空值的实现方法)
    [Java123] Java的异常处理机制
    [ORACLE123] sysdate处理
    Linux进程实时可视化监控
    【Java123】ThreadLocal学习笔记
    【XML123】了解XMLUnit
    字符串%s
  • 原文地址:https://www.cnblogs.com/tsecer/p/10485987.html
Copyright © 2020-2023  润新知