• 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;
    		}
    	}
    }
    
  • 相关阅读:
    # beta冲刺(2/7)
    # beta冲刺(1/7)
    福大软工 · 最终作业
    软工随笔纪实 《个人日志》
    beta答辩总结
    beta冲刺(6/7)
    (beta冲刺5/7)
    beta冲刺(4/7)
    beta冲刺(3/7)
    beta冲刺(2/7)
  • 原文地址:https://www.cnblogs.com/ukernel/p/9191039.html
Copyright © 2020-2023  润新知