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;
		}
	}
}

posted on 2018-06-16 18:20  ukernel  阅读(1357)  评论(0编辑  收藏  举报

导航