TCP/IP详解V2(六)之TCP协议

TCP输入

tcp_input

void
tcp_input(m, iphlen)
    register struct mbuf *m;
    int iphlen;
{
    register struct tcpiphdr *ti;
    register struct inpcb *inp;
    caddr_t optp = NULL;
    int optlen;
    int len, tlen, off;
    register struct tcpcb *tp = 0;
    register int tiflags;
    struct socket *so;
    int todrop, acked, ourfinisacked, needoutput = 0;
    short ostate;
    struct in_addr laddr;
    int dropsocket = 0;
    int iss = 0;
    u_long tiwin, ts_val, ts_ecr;
    int ts_present = 0;

    tcpstat.tcps_rcvtotal++;        //记录全局变量
    ti = mtod(m, struct tcpiphdr *);        //将mbuf中的信息转化为TCP/IP Header
    if (iphlen > sizeof (struct ip))        //如果存在IP选型,丢弃IP选项
        ip_stripoptions(m, (struct mbuf *)0);
    if (m->m_len < sizeof (struct tcpiphdr)) {        //如果首部mbuf中数据小于40字节,将外部簇中的数据调整到首部mbuf中
        if ((m = m_pullup(m, sizeof (struct tcpiphdr))) == 0) {
            tcpstat.tcps_rcvshort++;
            return;
        }
        ti = mtod(m, struct tcpiphdr *);        //将mbuf中的数据转化为ti
    }

    tlen = ((struct ip *)ti)->ip_len;        //除去IP首部外的数据
    len = sizeof (struct ip) + tlen;    //算上IP首部的数据
    ti->ti_next = ti->ti_prev = 0;        //已经将数据报提交给了协议层,所以TCP头部的信息基本上没用了
    ti->ti_x1 = 0;
    ti->ti_len = (u_short)tlen;
    HTONS(ti->ti_len);
    if (ti->ti_sum = in_cksum(m, len)) {        //计算校验和
        tcpstat.tcps_rcvbadsum++;
        goto drop;
    }
#endif /* TUBA_INCLUDE */

    off = ti->ti_off << 2;        //获取TCP首部的长度
    if (off < sizeof (struct tcphdr) || off > tlen) {
        tcpstat.tcps_rcvbadoff++;
        goto drop;
    }
    tlen -= off;        //减去TCP首部的信息,获取数据长度
    ti->ti_len = tlen;
    if (off > sizeof (struct tcphdr)) {        //如果TCP首部长度 > 20字节,意味着存在TCP选项
        if (m->m_len < sizeof(struct ip) + off) {        //如果首部mbuf中的长度小于TCP首部长度+TCP首部选项长度+IP首部长度
            if ((m = m_pullup(m, sizeof (struct ip) + off)) == 0) {        //调用函数将数据完整的copy进mbuf中
                tcpstat.tcps_rcvshort++;
                return;
            }
            ti = mtod(m, struct tcpiphdr *);        
        }
        optlen = off - sizeof (struct tcphdr);        //获取TCP选项长度
        optp = mtod(m, caddr_t) + sizeof (struct tcpiphdr);        //optp是指向第一个选项的指针
        
        if ((optlen == TCPOLEN_TSTAMP_APPA ||
             (optlen > TCPOLEN_TSTAMP_APPA &&
            optp[TCPOLEN_TSTAMP_APPA] == TCPOPT_EOL)) &&
             *(u_long *)optp == htonl(TCPOPT_TSTAMP_HDR) &&
             (ti->ti_flags & TH_SYN) == 0) {        //快速的处理时间戳选项
            ts_present = 1;
            ts_val = ntohl(*(u_long *)(optp + 4));
            ts_ecr = ntohl(*(u_long *)(optp + 8));
            optp = NULL;    /* we've parsed the options */
        }
    }
    tiflags = ti->ti_flags;

    /*
     * Convert TCP protocol specific fields to host format.
     */
    NTOHL(ti->ti_seq);        //将seq,ack,win以及urp转换为主机字节序
    NTOHL(ti->ti_ack);
    NTOHS(ti->ti_win);
    NTOHS(ti->ti_urp);

    /*
     * Locate pcb for segment.
     */
findpcb:
    inp = tcp_last_inpcb;        //获取上一次的Internet PCB
    if (inp->inp_lport != ti->ti_dport ||
        inp->inp_fport != ti->ti_sport ||
        inp->inp_faddr.s_addr != ti->ti_src.s_addr ||
        inp->inp_laddr.s_addr != ti->ti_dst.s_addr) {        //寻找合适的四元组
        inp = in_pcblookup(&tcb, ti->ti_src, ti->ti_sport,
            ti->ti_dst, ti->ti_dport, INPLOOKUP_WILDCARD);
        if (inp)        //找到的话,调整last PCB
            tcp_last_inpcb = inp;
        ++tcpstat.tcps_pcbcachemiss;
    }

    if (inp == 0)        //如果没有找到话,丢弃报文段,并发送RST
        goto dropwithreset;
    tp = intotcpcb(inp);        //获取TCP PCB
    if (tp == 0)        //如果没有找到TCP PCB,说明插口已经关闭,丢弃数据报并发送RST
        goto dropwithreset;
    if (tp->t_state == TCPS_CLOSED)        //如果目前的状态为CLOSED,直接丢弃数据报。
        goto drop;
    
    if ((tiflags & TH_SYN) == 0)        //如果置位了窗口大小改变选项,调整窗口大小,只有在SYN报文段中才能支持窗口大小改变选项
        tiwin = ti->ti_win << tp->snd_scale;
    else
        tiwin = ti->ti_win;

    so = inp->inp_socket;        //获取插口
    if (so->so_options & (SO_DEBUG|SO_ACCEPTCONN)) {        //如果插口正处于调试状态,保存TCP与IP的首部
        if (so->so_options & SO_DEBUG) {
            ostate = tp->t_state;
            tcp_saveti = *ti;
        }
        if (so->so_options & SO_ACCEPTCONN) {        //如果目前处于ACCEPTION状态,调用函数分配新的插口,Internet PCB和TCP PCB
            so = sonewconn(so, 0);
            if (so == 0)
                goto drop;

            dropsocket++;
            inp = (struct inpcb *)so->so_pcb;        //填充本地地址与端口
            inp->inp_laddr = ti->ti_dst;
            inp->inp_lport = ti->ti_dport;
#if BSD>=43
            inp->inp_options = ip_srcroute();        //获取数据报源路由选项
#endif
            tp = intotcpcb(inp);
            tp->t_state = TCPS_LISTEN;        //将新创建的SOCKET状态调整为LISTEN

            while (tp->request_r_scale < TCP_MAX_WINSHIFT &&
               TCP_MAXWIN << tp->request_r_scale < so->so_rcv.sb_hiwat)        //计算窗口的缩放因子
                tp->request_r_scale++;
        }
    }

    tp->t_idle = 0;        //将空闲时间置0
    tp->t_timer[TCPT_KEEP] = tcp_keepidle;        //重新设置Keep-Alive选项

    if (optp && tp->t_state != TCPS_LISTEN)        //如果存在选项,并且当前的状态不处于LISTEN状态,处理TCP选项
        tcp_dooptions(tp, optp, optlen, ti,
            &ts_present, &ts_val, &ts_ecr);

    if (tp->t_state == TCPS_ESTABLISHED &&
        (tiflags & (TH_SYN|TH_FIN|TH_RST|TH_URG|TH_ACK)) == TH_ACK &&
        (!ts_present || TSTMP_GEQ(ts_val, tp->ts_recent)) &&
        ti->ti_seq == tp->rcv_nxt &&
        tiwin && tiwin == tp->snd_wnd &&
        tp->snd_nxt == tp->snd_max) {        //如果处于连接状态,没有设置特殊的选项,携带的时间戳选项必须大于以前收到的值,收到的seq=期望的seq,通告窗口存在并等于发送时的窗口,下一个发送的报文段不是重传报文段
        if (ts_present && SEQ_LEQ(ti->ti_seq, tp->last_ack_sent) &&
           SEQ_LT(tp->last_ack_sent, ti->ti_seq + ti->ti_len)) {        //如果存在时间戳选项,根据时间戳选项更新TCP PCB中的信息
            tp->ts_recent_age = tcp_now;
            tp->ts_recent = ts_val;
        }

        if (ti->ti_len == 0) {        //处理ACK
            if (SEQ_GT(ti->ti_ack, tp->snd_una) &&
                SEQ_LEQ(ti->ti_ack, tp->snd_max) &&
                tp->snd_cwnd >= tp->snd_wnd) {        //如果确认的seq > 未确认的seq,确认的ack < 最大序号,拥塞窗口大于目前的窗口,即窗口完全打开,不处于慢启动和拥塞避免状态
                ++tcpstat.tcps_predack;        //更新全局变量
                if (ts_present)    //如果存在事件戳选项,更新RTT值
                    tcp_xmit_timer(tp, tcp_now-ts_ecr+1);        
                else if (tp->t_rtt &&
                        SEQ_GT(ti->ti_ack, tp->t_rtseq))
                    tcp_xmit_timer(tp, tp->t_rtt);
                acked = ti->ti_ack - tp->snd_una;        //计算确认的字节数
                tcpstat.tcps_rcvackpack++;        //修改全局变量
                tcpstat.tcps_rcvackbyte += acked;
                sbdrop(&so->so_snd, acked);        //从发送缓存中丢弃已经确认的数据
                tp->snd_una = ti->ti_ack;        //更新未确认的数值为刚收到的ACK
                m_freem(m);    //释放mbuf

                if (tp->snd_una == tp->snd_max)        //如果确定了所有的数据,关闭重传定时器
                    tp->t_timer[TCPT_REXMT] = 0;
                else if (tp->t_timer[TCPT_PERSIST] == 0)        //如果持续定时器没有设定,启动重传定时器
                    tp->t_timer[TCPT_REXMT] = tp->t_rxtcur;

                if (so->so_snd.sb_flags & SB_NOTIFY)        //唤醒等待发送数据的进程
                    sowwakeup(so);
                if (so->so_snd.sb_cc)        //如果发送缓存中依旧存在数据,发送数据,然后返回
                    (void) tcp_output(tp);
                return;
            }
        } else if (ti->ti_ack == tp->snd_una &&
            tp->seg_next == (struct tcpiphdr *)tp &&
            ti->ti_len <= sbspace(&so->so_rcv)) {        //如果收到的报文端的数据>0,ACK是没有被确认的数据,不存在乱序的报文段,并且接收缓存可以容纳所有的数据
            ++tcpstat.tcps_preddat;        //更新全局计数器以及接收缓存
            tp->rcv_nxt += ti->ti_len;
            tcpstat.tcps_rcvpack++;
            tcpstat.tcps_rcvbyte += ti->ti_len;
            /*
             * Drop TCP, IP headers and TCP options then add data
             * to socket buffer.
             */
            m->m_data += sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr);
            m->m_len -= sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr);
            sbappend(&so->so_rcv, m);        //将数据copy 进mbuf中
            sorwakeup(so);        //唤醒等待接收数据的进程
            tp->t_flags |= TF_DELACK;        //置位延迟ACK标志位
            return;
        }
    }

        //首部预测失败时,执行的代码
    m->m_data += sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr);        //丢弃TCP,IP以及TCP选项的数据       
    m->m_len  -= sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr);

    { int win;

    win = sbspace(&so->so_rcv);        //计算接收窗口缓存中的可用字节数
    if (win < 0)
        win = 0;
    tp->rcv_wnd = max(win, (int)(tp->rcv_adv - tp->rcv_nxt));        //获取通告给对方的窗口和接收窗口较大的一个,为了当前的接收窗口不小于当前的通告窗口
    }

    switch (tp->t_state) {
    case TCPS_LISTEN: {        //如果当前的状态处于LISTEN
        struct mbuf *am;
        register struct sockaddr_in *sin;

        if (tiflags & TH_RST)        //如果置位了RST,丢弃
            goto drop;
        if (tiflags & TH_ACK)        //如果收到了ACK,丢弃并发送RST
            goto dropwithreset;
        if ((tiflags & TH_SYN) == 0)        //丢弃一切非SYN的标志
            goto drop;
    
        if (m->m_flags & (M_BCAST|M_MCAST) ||
            IN_MULTICAST(ti->ti_dst.s_addr))        //如果收到了多播或者广播之类的数据报,丢弃
            goto drop;
        am = m_get(M_DONTWAIT, MT_SONAME);      //分配一个mbuf,保存sockaddr_in结构,其中带有客户端IP和端口号
        if (am == NULL)
            goto drop;
        am->m_len = sizeof (struct sockaddr_in);        //将mbuf的长度设置为sockaddr_in
        sin = mtod(am, struct sockaddr_in *);
        sin->sin_family = AF_INET;        //填充sockaddr_in中的信息
        sin->sin_len = sizeof(*sin);
        sin->sin_addr = ti->ti_src;
        sin->sin_port = ti->ti_sport;
        bzero((caddr_t)sin->sin_zero, sizeof(sin->sin_zero));
        laddr = inp->inp_laddr;        //设定TCP中的本地地址
        if (inp->inp_laddr.s_addr == INADDR_ANY)    //如果PCB中的本地地址为ANY
            inp->inp_laddr = ti->ti_dst;        //设置为准确的地址
        if (in_pcbconnect(inp, am)) {        //设置Internet PCB中的远程地址
            inp->inp_laddr = laddr;         //如果失败,丢弃报文段 ,等待对方发送超时
            (void) m_free(am);
            goto drop;
        }
        (void) m_free(am);      //填充完成,释放作为临时存储的mbuf
        tp->t_template = tcp_template(tp);        //设置新分配的TCP PCB中的TCP IP header
        if (tp->t_template == 0) {        //如果失败
            tp = tcp_drop(tp, ENOBUFS);        //丢弃报文段,等待对方发送超时
            dropsocket = 0;     
            goto drop;
        }
        if (optp)        //如果存在TCP选项,丢弃选项
            tcp_dooptions(tp, optp, optlen, ti,
                &ts_present, &ts_val, &ts_ecr);
        if (iss)        //设置ISS
            tp->iss = iss;
        else
            tp->iss = tcp_iss;
        tcp_iss += TCP_ISSINCR/2;
        tp->irs = ti->ti_seq;        //设置初始序列号
        tcp_sendseqinit(tp);        //初始化发送相关的序列号
        tcp_rcvseqinit(tp);        //初始化接收相关的序列号
        tp->t_flags |= TF_ACKNOW;        //立刻发送ACK
        tp->t_state = TCPS_SYN_RECEIVED;        //将目前的状态转化我SYN_RECV
        tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT;        //重新设置Keep-Alive
        dropsocket = 0;     //跳转到接下来的位置,完成对SYN报文段的处理
        tcpstat.tcps_accepts++;
        goto trimthenstep6;
        }

    case TCPS_SYN_SENT:        //已经发送了SYN报文段,等待接收SYN报文段以及对发送的SYN报文段的ACK
        if ((tiflags & TH_ACK) &&
            (SEQ_LEQ(ti->ti_ack, tp->iss) ||
             SEQ_GT(ti->ti_ack, tp->snd_max)))        //如果收到ACK,ACK小于发送的ISS或者ACK大于最大的发送值,丢弃,并发送RST
            goto dropwithreset;
        if (tiflags & TH_RST) {        //如果收到了RST,并且有携带ACK,说明对端拒绝连接,对端的服务没有启动,丢弃连接并返回差错
            if (tiflags & TH_ACK)
                tp = tcp_drop(tp, ECONNREFUSED);
            goto drop;        //如果不携带ACK的话,丢弃连接就好了,等待对方的发送超时
        }
        if ((tiflags & TH_SYN) == 0)        //如果SYN没有置位,丢弃
            goto drop;
        if (tiflags & TH_ACK) {        //如果SYN和ACK都已经置位
            tp->snd_una = ti->ti_ack;        //将未确认的seq修改为收到的ACK
            if (SEQ_LT(tp->snd_nxt, tp->snd_una))    //如果待发送的next小于未被确认的数据,调整待发送的数据
                tp->snd_nxt = tp->snd_una;        
        }
        tp->t_timer[TCPT_REXMT] = 0;        //关闭连接建立定时器
        tp->irs = ti->ti_seq;    //初始序号从接收的报文段中获取
        tcp_rcvseqinit(tp);
        tp->t_flags |= TF_ACKNOW;        //将立刻发送ACK置位
        if (tiflags & TH_ACK && SEQ_GT(tp->snd_una, tp->iss)) {        //如果收到了ACK并且uma > 连接的ISS(第二个条件是多余的),说明连接已经建立
            tcpstat.tcps_connects++;        //更新全局计数
            soisconnected(so);        //设置socket进入连接状态
            tp->t_state = TCPS_ESTABLISHED;
        
            if ((tp->t_flags & (TF_RCVD_SCALE|TF_REQ_SCALE)) ==
                (TF_RCVD_SCALE|TF_REQ_SCALE)) {        //确定是否远端使用窗口放大选项
                tp->snd_scale = tp->requested_s_scale;
                tp->rcv_scale = tp->request_r_scale;
            }
            (void) tcp_reass(tp, (struct tcpiphdr *)0,
                (struct mbuf *)0);        //如果数据在连接尚未建立之前到达,将数据放到接收缓存中
            
            if (tp->t_rtt)        //更新RTT计数器
                tcp_xmit_timer(tp, tp->t_rtt);
        } else
            tp->t_state = TCPS_SYN_RECEIVED;        //处理同时打开的情况

trimthenstep6:        //处理SYN中携带数据的情况
        ti->ti_seq++;        //收到了SYN,递增seq
        if (ti->ti_len > tp->rcv_wnd) {        //如果收到的数据大于接收窗口的大小
            todrop = ti->ti_len - tp->rcv_wnd;        //获取超出接收窗口的数据的长度
            m_adj(m, -todrop);        //丢弃超出窗口的长度
            ti->ti_len = tp->rcv_wnd;        //调整收到的数据的长度
            tiflags &= ~TH_FIN;        //置位FIN标记
            tcpstat.tcps_rcvpackafterwin++;        //修改全局数据
            tcpstat.tcps_rcvbyteafterwin += todrop;
        }
        tp->snd_wl1 = ti->ti_seq - 1;        //强制更新窗口变量
        tp->rcv_up = ti->ti_seq;
        goto step6;
    }

    if (ts_present && (tiflags & TH_RST) == 0 && tp->ts_recent &&
        TSTMP_LT(ts_val, tp->ts_recent)) {        //处理序号回绕的情况。需要只有32位表示,如果发送速率过快的话,很快就会出现数据回绕的情况
        if ((int)(tcp_now - tp->ts_recent_age) > TCP_PAWS_IDLE) {
            tp->ts_recent = 0;
        } else {
            tcpstat.tcps_rcvduppack++;
            tcpstat.tcps_rcvdupbyte += ti->ti_len;
            tcpstat.tcps_pawsdrop++;
            goto dropafterack;
        }
    }

    todrop = tp->rcv_nxt - ti->ti_seq;        //如果发送的数据存在之前已经发送的数据,需要裁剪新到达的数据报
    if (todrop > 0) {        //如果确实存在重复的数据
        if (tiflags & TH_SYN) {    //如果已经置位了SYN标志
            tiflags &= ~TH_SYN;        //清除重复的SYN数据报
            ti->ti_seq++;    //递增seq
            if (ti->ti_urp > 1)         //如果存在紧急数据偏移量
                ti->ti_urp--;
            else
                tiflags &= ~TH_URG;        //如果不存在,置位URG标志
            todrop--;
        }
        if (todrop >= ti->ti_len) {        //如果数据报完全重复
            tcpstat.tcps_rcvduppack++;        //更新全局统计量
            tcpstat.tcps_rcvdupbyte += ti->ti_len;
            
            if ((tiflags & TH_FIN && todrop == ti->ti_len + 1)){        //测试FIN是否重复
                todrop = ti->ti_len;        //忽略重复的FIN
                tiflags &= ~TH_FIN;        //清除FIN标记,并且立刻发送ACK标记
                tp->t_flags |= TF_ACKNOW;        
            } else {
                if (todrop != 0 || (tiflags & TH_ACK) == 0)        //如果携带的数据全部是重复的,并且没有确认本地已发送数据的ACK
                    goto dropafterack;        //丢弃数据,并发送ACK
            }
        } else {
            tcpstat.tcps_rcvpartduppack++;        //更新全局统计量
            tcpstat.tcps_rcvpartdupbyte += todrop;
        }
        m_adj(m, todrop);        //调整丢弃多余的数据量
        ti->ti_seq += todrop;
        ti->ti_len -= todrop;
        if (ti->ti_urp > todrop)    //删除重复的数据,更新紧急数据
            ti->ti_urp -= todrop;
        else {
            tiflags &= ~TH_URG;
            ti->ti_urp = 0;
        }
    }

    if ((so->so_state & SS_NOFDREF) &&
        tp->t_state > TCPS_CLOSE_WAIT && ti->ti_len) {        //如果应用程序已经关闭连接,在新收到数据的时候,直接关闭连接,并向对方发送RST
        tp = tcp_close(tp);
        tcpstat.tcps_rcvafterclose++;
        goto dropwithreset;
    }

    todrop = (ti->ti_seq+ti->ti_len) - (tp->rcv_nxt+tp->rcv_wnd);        //计算落在通告窗口右侧的字节数
    if (todrop > 0) {        //如果有重复的数据
        tcpstat.tcps_rcvpackafterwin++;
        if (todrop >= ti->ti_len) {        //如果数据完全重复
            tcpstat.tcps_rcvbyteafterwin += ti->ti_len;        
            if (tiflags & TH_SYN &&
                tp->t_state == TCPS_TIME_WAIT &&
                SEQ_GT(ti->ti_seq, tp->rcv_nxt)) {        //如果携带SYN,当前状态处于TIME_WAIT,收到的序列号>期望接受的序列号
                iss = tp->rcv_nxt + TCP_ISSINCR;        //说明对端在连接即将被关闭的情况下想要重新创建连接
                tp = tcp_close(tp);        //释放已经存在的Internet PCB和TCP PCB,并跳转到起始处重新建立连接
                goto findpcb;
            }

            if (tp->rcv_wnd == 0 && ti->ti_seq == tp->rcv_nxt) {        //如果接收窗口没有空间,并且发送的数据seq等于接收端准备接收的数据,意味着,此次接受到的是一个窗口探测报文
                tp->t_flags |= TF_ACKNOW;        //立刻发送ACK确认报文
                tcpstat.tcps_rcvwinprobe++;
            } else        //否则的话报文在窗口之外并且非窗口探测报文,则应该丢弃这个报文段
                goto dropafterack;
        } else
            tcpstat.tcps_rcvbyteafterwin += todrop;
        m_adj(m, -todrop);        //从mbuf链中移除落在右侧的报文段,更新收到的报文段长度
        ti->ti_len -= todrop;
        tiflags &= ~(TH_PUSH|TH_FIN);        //将SYN和FIN置位
    }

    if (ts_present && SEQ_LEQ(ti->ti_seq, tp->last_ack_sent) &&
        SEQ_LT(tp->last_ack_sent, ti->ti_seq + ti->ti_len +
           ((tiflags & (TH_SYN|TH_FIN)) != 0))) {        //还是处理TCP的时间戳选项
        tp->ts_recent_age = tcp_now;
        tp->ts_recent = ts_val;
    }

    if (tiflags&TH_RST) switch (tp->t_state) {        //如果收到了RST标志,根据此刻的状态,做不同的处理。最后都会关闭SOCKET,区别在于会返回不同的错误

    case TCPS_SYN_RECEIVED:        //如果在连接未建立阶段出现RST,直接关闭
        so->so_error = ECONNREFUSED;
        goto close;

    case TCPS_ESTABLISHED:    //如果在连接状态或者在连接未完全关闭阶段出现RST,直接关闭
    case TCPS_FIN_WAIT_1:
    case TCPS_FIN_WAIT_2:
    case TCPS_CLOSE_WAIT:
        so->so_error = ECONNRESET;
    close:
        tp->t_state = TCPS_CLOSED;
        tcpstat.tcps_drops++;
        tp = tcp_close(tp);
        goto drop;

    case TCPS_CLOSING:
    case TCPS_LAST_ACK:
    case TCPS_TIME_WAIT:
        tp = tcp_close(tp);
        goto drop;
    }

    if (tiflags & TH_SYN) {        //如果此时的SYN依旧置位,丢弃报文并向对端发送RST
        tp = tcp_drop(tp, ECONNRESET);
        goto dropwithreset;
    }

    if ((tiflags & TH_ACK) == 0)    //如果ACK没有置位,直接丢弃连接
        goto drop;
    
        //接下来是根据ACK更新窗口的数据
    switch (tp->t_state) {
    case TCPS_SYN_RECEIVED:        //如果在Recv状态下收到了ACK。这种情况下,将处理被动打开以及同时打开的情况。
        if (SEQ_GT(tp->snd_una, ti->ti_ack) ||        
            SEQ_GT(ti->ti_ack, tp->snd_max))        //如果未确认的seq > 接收到的ACK或者收到的ACK > 接收端的max seq,丢弃报文段并发送RST
            goto dropwithreset;        //一般情况下,收到的ACK应该介于未确认的seq以及max seq之间
        tcpstat.tcps_connects++;
        soisconnected(so);        //唤醒在ACCEPT上等待的进程d
        tp->t_state = TCPS_ESTABLISHED;        //调整连接状态为ESTABLISHED

        if ((tp->t_flags & (TF_RCVD_SCALE|TF_REQ_SCALE)) ==
            (TF_RCVD_SCALE|TF_REQ_SCALE)) {        //如果设置了窗口大小选项,更新窗口大小
            tp->snd_scale = tp->requested_s_scale;
            tp->rcv_scale = tp->request_r_scale;
        }
        (void) tcp_reass(tp, (struct tcpiphdr *)0, (struct mbuf *)0);        //将乱序队列中的数据提交给recvbuf
        tp->snd_wl1 = ti->ti_seq - 1;        

    case TCPS_ESTABLISHED:
    case TCPS_FIN_WAIT_1:
    case TCPS_FIN_WAIT_2:
    case TCPS_CLOSE_WAIT:
    case TCPS_CLOSING:
    case TCPS_LAST_ACK:
    case TCPS_TIME_WAIT:        
                //处理重复的ACK
        if (SEQ_LEQ(ti->ti_ack, tp->snd_una)) {        //如果收到的ACK小于未确认的seq
            if (ti->ti_len == 0 && tiwin == tp->snd_wnd) {        //如果没有携带数据通告窗口的大小没有改变
                tcpstat.tcps_rcvdupack++;        //增加重复ACK的计数

                if (tp->t_timer[TCPT_REXMT] == 0 ||
                    ti->ti_ack != tp->snd_una)        //如果收到的ACK小于uma或者重传定时器没有设置
                    tp->t_dupacks = 0;        //将重复的ACK计数置0
                else if (++tp->t_dupacks == tcprexmtthresh) {        //如果重复的ACK == 3,指向拥塞避免算法
                    tcp_seq onxt = tp->snd_nxt;        //获取下一个即将发送的数值
                    u_int win =
                        min(tp->snd_wnd, tp->snd_cwnd) / 2 /
                        tp->t_maxseg;        //窗口设置为拥塞窗口或者当前发送窗口中的最小值的一半

                    if (win < 2)        //窗口最小为2
                        win = 2;
                    tp->snd_ssthresh = win * tp->t_maxseg;        //将慢启动门限设置为:拥塞窗口与发送窗口中较小值的一半
                        tp->t_timer[TCPT_REXMT] = 0;        //关闭重传计数器
                    tp->t_rtt = 0;        //将RTT置0
                    tp->snd_nxt = ti->ti_ack;        //将即将发送的seq修改为收到的ack,也就丢失的报文段(对方期待的报文段)
                    tp->snd_cwnd = tp->t_maxseg;        //将拥塞窗口设置为MSS
                    (void) tcp_output(tp);        //发送数据
                    tp->snd_cwnd = tp->snd_ssthresh +
                           tp->t_maxseg * tp->t_dupacks;        //调整拥塞窗口为慢启动门限+3×MSS
                    if (SEQ_GT(onxt, tp->snd_nxt))        //将待发送seq调整为之前保存的seq
                        tp->snd_nxt = onxt;
                    goto drop;        //然后丢弃本次收到的ACK
                } else if (tp->t_dupacks > tcprexmtthresh) {        //如果重复的ACK计数 > 3
                    tp->snd_cwnd += tp->t_maxseg;    //每次收到一个ACK,就递增拥塞窗口
                    (void) tcp_output(tp);        //发送数据
                    goto drop;        //丢弃本次收到的ACK
                }
            } else
                tp->t_dupacks = 0;        //收到的重复ACK中带有数据,不是一个单纯的ACK计数作用
            break;
        }
        
        if (tp->t_dupacks > tcprexmtthresh &&
            tp->snd_cwnd > tp->snd_ssthresh)        //如果收到的重复的ACK计数 > 3,说明这是在收到了四个或者四个以上的ACK后,收到的第一个非重复的ACK。快速重传算法结束
            tp->snd_cwnd = tp->snd_ssthresh;        //调整拥塞窗口为慢启动门限
        tp->t_dupacks = 0;        //将重复的ACK计数置0
        if (SEQ_GT(ti->ti_ack, tp->snd_max)) {        //如果收到的ACK大于还没有发送的数据。可能是高速网络的回绕。
            tcpstat.tcps_rcvacktoomuch++;        //丢弃数据报并回复一个ACK
            goto dropafterack;
        }
        acked = ti->ti_ack - tp->snd_una;    //记录收到的ACK确认的数据量
        tcpstat.tcps_rcvackpack++;
        tcpstat.tcps_rcvackbyte += acked;

        if (ts_present)        //如果存在时间戳选项,记录并更新RTT
            tcp_xmit_timer(tp, tcp_now-ts_ecr+1);
        else if (tp->t_rtt && SEQ_GT(ti->ti_ack, tp->t_rtseq))
            tcp_xmit_timer(tp,tp->t_rtt);

        if (ti->ti_ack == tp->snd_max) {如果已发送了所有数据,关闭重传定时器
            tp->t_timer[TCPT_REXMT] = 0;
            needoutput = 1;        
        } else if (tp->t_timer[TCPT_PERSIST] == 0)        //如果持续定时器已经被关闭
            tp->t_timer[TCPT_REXMT] = tp->t_rxtcur;        //重启重传定时器
    
        {
        register u_int cw = tp->snd_cwnd;    //获取拥塞窗口大小
        register u_int incr = tp->t_maxseg;        //获取MSS

        if (cw > tp->snd_ssthresh)        //如果拥塞窗口 > 慢启动门限,拥塞窗口的更新速度减慢
            incr = incr * incr / cw + incr / 8;
        tp->snd_cwnd = min(cw + incr, TCP_MAXWIN<<tp->snd_scale);
        }
        if (acked > so->so_snd.sb_cc) {        //如果确认的字节数 > 发送缓存中的字节数
            tp->snd_wnd -= so->so_snd.sb_cc;        //在发送窗口中减去已经被确认的缓存
            sbdrop(&so->so_snd, (int)so->so_snd.sb_cc);        //从socket的缓存中删除所有的字节
            ourfinisacked = 1;
        } else {
            sbdrop(&so->so_snd, acked);        //如果发送缓存中的数据没有被完全的确认,从socket的缓存中丢弃已经被确认的字节数
            tp->snd_wnd -= acked;        //从发送窗口减去发送的字节数
            ourfinisacked = 0;
        }
        if (so->so_snd.sb_flags & SB_NOTIFY)        //唤醒等待发送的进程
            sowwakeup(so);
        tp->snd_una = ti->ti_ack;        //调整未被确认的数据为接收到的ACK seq
        if (SEQ_LT(tp->snd_nxt, tp->snd_una))        //调整next seq
            tp->snd_nxt = tp->snd_una;

                //处理四种特殊情况下的ACK
        switch (tp->t_state) {        
            case TCPS_FIN_WAIT_1:        //如果处于FIN_WAIT_1时,收到ACK
            if (ourfinisacked) {        //如果数据已经发送完毕
                if (so->so_state & SS_CANTRCVMORE){        //将状态转换为FIN_WAIT_2,并将FIN_WAIT_2定时器置位
                    soisdisconnected(so);
                    tp->t_timer[TCPT_2MSL] = tcp_maxidle;
                }
                tp->t_state = TCPS_FIN_WAIT_2;
            }
            break;

        case TCPS_CLOSING:        //如果此时的状态处于CLOSING
            if (ourfinisacked) {    
                tp->t_state = TCPS_TIME_WAIT;        //将状态转换为TIME_WAIT
                tcp_canceltimers(tp);        //取消所有的定时器
                tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL;        //启动2MSL定时器
                soisdisconnected(so);        //关闭插口
            }
            break;

        case TCPS_LAST_ACK:        //如果被动关闭端收到ACK
            if (ourfinisacked) {        //如果数据已经发送完毕
                tp = tcp_close(tp);        //直接关闭插口
                goto drop;
            }
            break;

        case TCPS_TIME_WAIT:        //如果处于TIME_WAIT状态
            tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL;        //对TIME_WAIT重新计数
            goto dropafterack;        //然后丢弃数据报并发送ACK
        }
    }

step6:
    if ((tiflags & TH_ACK) &&
        (SEQ_LT(tp->snd_wl1, ti->ti_seq) || tp->snd_wl1 == ti->ti_seq &&
        (SEQ_LT(tp->snd_wl2, ti->ti_ack) ||
         tp->snd_wl2 == ti->ti_ack && tiwin > tp->snd_wnd))) {        //这么多判断的目的是为了更新发送窗口,首先,接收到的是一个ACK等等的条件
        /* keep track of pure window updates */
        if (ti->ti_len == 0 &&
            tp->snd_wl2 == ti->ti_ack && tiwin > tp->snd_wnd)        //报文段没有携带数据,但是报文段确认了新的数据
            tcpstat.tcps_rcvwinupd++;
        tp->snd_wnd = tiwin;        //更新发送窗口
        tp->snd_wl1 = ti->ti_seq;        //更新最后接收到的报文段序号
        tp->snd_wl2 = ti->ti_ack;        //更新最后接受到的ACK序号
        if (tp->snd_wnd > tp->max_sndwnd)        //调整max seq的位置
            tp->max_sndwnd = tp->snd_wnd;
        needoutput = 1;        //清理了发送缓存,意味着可以继续发送数据
    }

    if ((tiflags & TH_URG) && ti->ti_urp &&
        TCPS_HAVERCVDFIN(tp->t_state) == 0) {        //处理URG数据,不重点关注
        if (ti->ti_urp + so->so_rcv.sb_cc > sb_max) {
            ti->ti_urp = 0;         /* XXX */
            tiflags &= ~TH_URG;     /* XXX */
            goto dodata;            /* XXX */
        }

        if (SEQ_GT(ti->ti_seq+ti->ti_urp, tp->rcv_up)) {
            tp->rcv_up = ti->ti_seq + ti->ti_urp;
            so->so_oobmark = so->so_rcv.sb_cc +
                (tp->rcv_up - tp->rcv_nxt) - 1;
            if (so->so_oobmark == 0)
                so->so_state |= SS_RCVATMARK;
            sohasoutofband(so);
            tp->t_oobflags &= ~(TCPOOB_HAVEDATA | TCPOOB_HADDATA);
        }
        
        if (ti->ti_urp <= ti->ti_len
#ifdef SO_OOBINLINE
             && (so->so_options & SO_OOBINLINE) == 0
#endif
             )
            tcp_pulloutofband(so, ti, m);
    } else
        if (SEQ_GT(tp->rcv_nxt, tp->rcv_up))
            tp->rcv_up = tp->rcv_nxt;
dodata:    //处理已经接受的数据

    if ((ti->ti_len || (tiflags&TH_FIN)) &&
        TCPS_HAVERCVDFIN(tp->t_state) == 0) {        //如果携带数据,或者FIN置位,并且连接还没有收到过FIN
        TCP_REASS(tp, ti, m, so, tiflags);        //判断是否需要将数据添加到乱序链表中
        len = so->so_rcv.sb_hiwat - (tp->rcv_adv - tp->rcv_nxt);        //计算对端发送缓存的大小
    } else {
        m_freem(m);
        tiflags &= ~TH_FIN;
    }

        //处理带有FIN标志的数据报
        //如果A向B发送了FIN,相当于A告诉B:我不打算接收你发送的数据了,但是我还有往里面写数据
        //B在接收到FIN之后:那我只读你写的数据,不往管道中发送数据了
    if (tiflags & TH_FIN) {        //
        if (TCPS_HAVERCVDFIN(tp->t_state) == 0) {        //如果是接收到的第一个FIN数据报
            socantrcvmore(so);        //将插口设置为只读
            tp->t_flags |= TF_ACKNOW;        //立刻发送ACK
            tp->rcv_nxt++;        //递增recv next
        }
        switch (tp->t_state) {    
        case TCPS_SYN_RECEIVED:
        case TCPS_ESTABLISHED:        //根据当前的状态设置在接收到FIN之后,状态如何变化
            tp->t_state = TCPS_CLOSE_WAIT;        //如果当前是ESTABLISHED,状态转变为:等待关闭
            break;

        case TCPS_FIN_WAIT_1:        //如果处于FIN_WAIT_1,转换为CLOSING,如果收到ACK,转换为FIN_WAIT_2
            tp->t_state = TCPS_CLOSING;
            break;

        case TCPS_FIN_WAIT_2:        //将状态转换为TIME_WAIT
            tp->t_state = TCPS_TIME_WAIT;
            tcp_canceltimers(tp);        //置位所有的定时器
            tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL;        //设置2MSL定时器
            soisdisconnected(so);        //关闭插口
            break;

        case TCPS_TIME_WAIT:        //如果此时处于TIME_WAIT状态,收到了重复的FIN,重新开始2MSL计时
            tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL;
            break;
        }
    }
    if (so->so_options & SO_DEBUG)        //如果记录了
        tcp_trace(TA_INPUT, ostate, tp, &tcp_saveti, 0);

    if (needoutput || (tp->t_flags & TF_ACKNOW))        //如果需要发送数据,就立刻发送数据
        (void) tcp_output(tp);
    return;

dropafterack:        //丢弃收到的数据并立刻发送ACK
    if (tiflags & TH_RST)
        goto drop;
    m_freem(m);
    tp->t_flags |= TF_ACKNOW;
    (void) tcp_output(tp);
    return;

dropwithreset:        //丢弃收到的数据并立刻发送RST
    if ((tiflags & TH_RST) || m->m_flags & (M_BCAST|M_MCAST) ||
        IN_MULTICAST(ti->ti_dst.s_addr))
        goto drop;
    if (tiflags & TH_ACK)
        tcp_respond(tp, ti, m, (tcp_seq)0, ti->ti_ack, TH_RST);
    else {
        if (tiflags & TH_SYN)
            ti->ti_len++;
        tcp_respond(tp, ti, m, ti->ti_seq+ti->ti_len, (tcp_seq)0,
            TH_RST|TH_ACK);
    }
    if (dropsocket)
        (void) soabort(so);
    return;

drop:        //丢弃收到的数据
    if (tp && (tp->t_inpcb->inp_socket->so_options & SO_DEBUG))
        tcp_trace(TA_DROP, ostate, tp, &tcp_saveti, 0);
    m_freem(m);
    /* destroy temporarily created socket */
    if (dropsocket)
        (void) soabort(so);
    return;
#ifndef TUBA_INCLUDE
}

tcp_dooptions

  • 功能A:处理TCP选项:EOL,NOP,MSS,窗口大小,时间戳

    void
    tcp_dooptions(tp, cp, cnt, ti, ts_present, ts_val, ts_ecr)
    struct tcpcb *tp;
    u_char *cp;
    int cnt;
    struct tcpiphdr *ti;
    int *ts_present;
    u_long *ts_val, *ts_ecr;
    {
    u_short mss;
    int opt, optlen;
    
    for (; cnt > 0; cnt -= optlen, cp += optlen) {
        opt = cp[0];
        if (opt == TCPOPT_EOL)
            break;
        if (opt == TCPOPT_NOP)
            optlen = 1;
        else {
            optlen = cp[1];
            if (optlen <= 0)
                break;
        }
        switch (opt) {
    
        default:
            continue;
    
        case TCPOPT_MAXSEG:        //处理MSS选项
            if (optlen != TCPOLEN_MAXSEG)        //如果MSS选项的长度!=4,结束处理
                continue;
            if (!(ti->ti_flags & TH_SYN))        //如果报文段不携带SYN选项,结束处理
                continue;
            bcopy((char *) cp + 2, (char *) &mss, sizeof(mss));        //将选项中的MSS copy进来,转换字节序,并调用tcp mss函数进行处理
            NTOHS(mss);
            (void) tcp_mss(tp, mss);    /* sets t_maxseg */
            break;
    
        case TCPOPT_WINDOW:        //如果携带窗口大小选项
            if (optlen != TCPOLEN_WINDOW)        //如果窗口大小!=4,结束处理
                continue;
            if (!(ti->ti_flags & TH_SYN))        //如果没有出现在SYN报文段中,结束处理
                continue;
            tp->t_flags |= TF_RCVD_SCALE;        //保存窗口的缩放因子,如果双方都支持窗口大小的选项处理,使用窗口缩放处理功能
            tp->requested_s_scale = min(cp[2], TCP_MAX_WINSHIFT);
            break;
    
        case TCPOPT_TIMESTAMP:        //处理时间戳选项
            if (optlen != TCPOLEN_TIMESTAMP)        //如果时间戳选项的长度!=10,结束处理
                continue;
            *ts_present = 1;        //标志时间戳选项
            bcopy((char *)cp + 2, (char *) ts_val, sizeof(*ts_val));        //保存时间戳的数值
            NTOHL(*ts_val);
            bcopy((char *)cp + 6, (char *) ts_ecr, sizeof(*ts_ecr));
            NTOHL(*ts_ecr);
    
            if (ti->ti_flags & TH_SYN) {    //如果在SYN选项中带有时间戳选项
                tp->t_flags |= TF_RCVD_TSTMP;        //置位标志位
                tp->ts_recent = *ts_val;        //填充时间戳
                tp->ts_recent_age = tcp_now;
            }
            break;
        }
    }
    }

猜你喜欢

转载自www.cnblogs.com/ukernel/p/9191039.html