tcp_input.c
来自「基于组件方式开发操作系统的OSKIT源代码」· C语言 代码 · 共 2,144 行 · 第 1/4 页
C
2,144 行
* this is a pure ack for outstanding data. */ ++tcpstat.tcps_predack; if ((to.to_flag & TOF_TS) != 0) tcp_xmit_timer(tp, tcp_now - to.to_tsecr + 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; m_freem(m); /* * If all outstanding data are acked, stop * retransmit timer, otherwise restart timer * using current (possibly backed-off) value. * If process is waiting for space, * wakeup/selwakeup/signal. If data * are ready to send, let tcp_output * decide between more output or persist. */ 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)) { /* * this is a pure, in-sequence data packet * with nothing on the reassembly queue and * we have enough buffer space to take it. */ ++tcpstat.tcps_preddat; tp->rcv_nxt += ti->ti_len; tcpstat.tcps_rcvpack++; tcpstat.tcps_rcvbyte += ti->ti_len; /* * Add data to socket buffer. */ sbappend(&so->so_rcv, m); sorwakeup(so);#ifdef TCP_ACK_HACK /* * If this is a short packet, then ACK now - with Nagel * congestion avoidance sender won't send more until * he gets an ACK. */ if (tiflags & TH_PUSH) { tp->t_flags |= TF_ACKNOW; tcp_output(tp); } else { tp->t_flags |= TF_DELACK; }#else tp->t_flags |= TF_DELACK;#endif return; } } /* * Calculate amount of space in receive window, * and then do TCP input processing. * Receive window is amount of space in rcv queue, * but not less than advertised window. */ { 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) { /* * If the state is LISTEN then ignore segment if it contains an RST. * If the segment contains an ACK then it is bad and send a RST. * If it does not contain a SYN then it is not interesting; drop it. * Don't bother responding if the destination was a broadcast. * Otherwise initialize tp->rcv_nxt, and tp->irs, select an initial * tp->iss, and send a segment: * <SEQ=ISS><ACK=RCV_NXT><CTL=SYN,ACK> * Also initialize tp->snd_nxt to tp->iss+1 and tp->snd_una to tp->iss. * Fill in remote peer address fields if not previously specified. * Enter SYN_RECEIVED state, and process any other fields of this * segment in this state. */ case TCPS_LISTEN: { struct mbuf *am; register struct sockaddr_in *sin; if (tiflags & TH_RST) goto drop; if (tiflags & TH_ACK) goto dropwithreset; if ((tiflags & TH_SYN) == 0) goto drop; /* * RFC1122 4.2.3.10, p. 104: discard bcast/mcast SYN * in_broadcast() should never return true on a received * packet with M_BCAST not set. */ if (m->m_flags & (M_BCAST|M_MCAST) || IN_MULTICAST(ntohl(ti->ti_dst.s_addr))) goto drop; am = m_get(M_DONTWAIT, MT_SONAME); /* XXX */ if (am == NULL) goto drop; am->m_len = sizeof (struct sockaddr_in); sin = mtod(am, struct sockaddr_in *); sin->sin_family = AF_INET; 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; if (inp->inp_laddr.s_addr == INADDR_ANY) inp->inp_laddr = ti->ti_dst; if (in_pcbconnect(inp, am)) { inp->inp_laddr = laddr; (void) m_free(am); goto drop; } (void) m_free(am); tp->t_template = tcp_template(tp); if (tp->t_template == 0) { tp = tcp_drop(tp, ENOBUFS); dropsocket = 0; /* socket is already gone */ goto drop; } if ((taop = tcp_gettaocache(inp)) == NULL) { taop = &tao_noncached; bzero(taop, sizeof(*taop)); } tcp_dooptions(tp, optp, optlen, ti, &to); if (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); /* * Initialization of the tcpcb for transaction; * set SND.WND = SEG.WND, * initialize CCsend and CCrecv. */ tp->snd_wnd = tiwin; /* initial send-window */ tp->cc_send = CC_INC(tcp_ccgen); tp->cc_recv = to.to_cc; /* * Perform TAO test on incoming CC (SEG.CC) option, if any. * - compare SEG.CC against cached CC from the same host, * if any. * - if SEG.CC > chached value, SYN must be new and is accepted * immediately: save new CC in the cache, mark the socket * connected, enter ESTABLISHED state, turn on flag to * send a SYN in the next segment. * A virtual advertised window is set in rcv_adv to * initialize SWS prevention. Then enter normal segment * processing: drop SYN, process data and FIN. * - otherwise do a normal 3-way handshake. */ if ((to.to_flag & TOF_CC) != 0) { if (taop->tao_cc != 0 && CC_GT(to.to_cc, taop->tao_cc)) { taop->tao_cc = to.to_cc; tp->t_state = TCPS_ESTABLISHED; /* * If there is a FIN, or if there is data and the * connection is local, then delay SYN,ACK(SYN) in * the hope of piggy-backing it on a response * segment. Otherwise must send ACK now in case * the other side is slow starting. */ if ((tiflags & TH_FIN) || (ti->ti_len != 0 && in_localaddr(inp->inp_faddr))) tp->t_flags |= (TF_DELACK | TF_NEEDSYN); else tp->t_flags |= (TF_ACKNOW | TF_NEEDSYN); /* * Limit the `virtual advertised window' to TCP_MAXWIN * here. Even if we requested window scaling, it will * become effective only later when our SYN is acked. */ tp->rcv_adv += min(tp->rcv_wnd, TCP_MAXWIN); tcpstat.tcps_connects++; soisconnected(so); tp->t_timer[TCPT_KEEP] = tcp_keepinit; dropsocket = 0; /* committed to socket */ tcpstat.tcps_accepts++; goto trimthenstep6; } /* else do standard 3-way handshake */ } else { /* * No CC option, but maybe CC.NEW: * invalidate cached value. */ taop->tao_cc = 0; } /* * TAO test failed or there was no CC option, * do a standard 3-way handshake. */ tp->t_flags |= TF_ACKNOW; tp->t_state = TCPS_SYN_RECEIVED; tp->t_timer[TCPT_KEEP] = tcp_keepinit; dropsocket = 0; /* committed to socket */ tcpstat.tcps_accepts++; goto trimthenstep6; } /* * If the state is SYN_RECEIVED: * do just the ack and RST checks from SYN_SENT state. * If the state is SYN_SENT: * if seg contains an ACK, but not for our SYN, drop the input. * if seg contains a RST, then drop the connection. * if seg does not contain SYN, then drop it. * Otherwise this is an acceptable SYN segment * initialize tp->rcv_nxt and tp->irs * if seg contains ack then advance tp->snd_una * if SYN has been acked change to ESTABLISHED else SYN_RCVD state * arrange for segment to be acked (eventually) * continue processing rest of data/controls, beginning with URG */ case TCPS_SYN_RECEIVED: case TCPS_SYN_SENT: if ((taop = tcp_gettaocache(inp)) == NULL) { taop = &tao_noncached; bzero(taop, sizeof(*taop)); } if ((tiflags & TH_ACK) && (SEQ_LEQ(ti->ti_ack, tp->iss) || SEQ_GT(ti->ti_ack, tp->snd_max))) { /* * If we have a cached CCsent for the remote host, * hence we haven't just crashed and restarted, * do not send a RST. This may be a retransmission * from the other side after our earlier ACK was lost. * Our new SYN, when it arrives, will serve as the * needed ACK. */ if (taop->tao_ccsent != 0) goto drop; else goto dropwithreset; } if (tiflags & TH_RST) { if (tiflags & TH_ACK) tp = tcp_drop(tp, ECONNREFUSED); goto drop; } if (tp->t_state == TCPS_SYN_RECEIVED) break; if ((tiflags & TH_SYN) == 0) goto drop; tp->snd_wnd = ti->ti_win; /* initial send window */ tp->cc_recv = to.to_cc; /* foreign CC */ tp->irs = ti->ti_seq; tcp_rcvseqinit(tp); if (tiflags & TH_ACK) { /* * Our SYN was acked. If segment contains CC.ECHO * option, check it to make sure this segment really * matches our SYN. If not, just drop it as old * duplicate, but send an RST if we're still playing * by the old rules. */ if ((to.to_flag & TOF_CCECHO) && tp->cc_send != to.to_ccecho) { if (taop->tao_ccsent != 0) goto drop; else goto dropwithreset; } tcpstat.tcps_connects++; soisconnected(so); /* Do window scaling on this connection? */ 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; } /* Segment is acceptable, update cache if undefined. */ if (taop->tao_ccsent == 0) taop->tao_ccsent = to.to_ccecho; tp->rcv_adv += tp->rcv_wnd; tp->snd_una++; /* SYN is acked */ /* * If there's data, delay ACK; if there's also a FIN * ACKNOW will be turned on later. */ if (ti->ti_len != 0) tp->t_flags |= TF_DELACK; else tp->t_flags |= TF_ACKNOW; /* * Received <SYN,ACK> in SYN_SENT[*] state. * Transitions: * SYN_SENT --> ESTABLISHED * SYN_SENT* --> FIN_WAIT_1 */ if (tp->t_flags & TF_NEEDFIN) { tp->t_state = TCPS_FIN_WAIT_1; tp->t_flags &= ~TF_NEEDFIN; tiflags &= ~TH_SYN; } else { tp->t_state = TCPS_ESTABLISHED; tp->t_timer[TCPT_KEEP] = tcp_keepidle; } } else { /* * Received initial SYN in SYN-SENT[*] state => simul- * taneous open. If segment contains CC option and there is * a cached CC, apply TAO test; if it succeeds, connection is * half-synchronized. Otherwise, do 3-way handshake: * SYN-SENT -> SYN-RECEIVED * SYN-SENT* -> SYN-RECEIVED* * If there was no CC option, clear cached CC value. */ tp->t_flags |= TF_ACKNOW; tp->t_timer[TCPT_REXMT] = 0; if (to.to_flag & TOF_CC) { if (taop->tao_cc != 0 && CC_GT(to.to_cc, taop->tao_cc)) { /* * update cache and make transition: * SYN-SENT -> ESTABLISHED* * SYN-SENT* -> FIN-WAIT-1* */ taop->tao_cc = to.to_cc; if (tp->t_flags & TF_NEEDFIN) { tp->t_state = TCPS_FIN_WAIT_1; tp->t_flags &= ~TF_NEEDFIN; } else { tp->t_state = TCPS_ESTABLISHED; tp->t_timer[TCPT_KEEP] = tcp_keepidle; } tp->t_flags |= TF_NEEDSYN; } else tp->t_state = TCPS_SYN_RECEIVED; } else { /* CC.NEW or no option => invalidate cache */ taop->tao_cc = 0; tp->t_state = TCPS_SYN_RECEIVED; } }trimthenstep6: /* * Advance ti->ti_seq to correspond to first data byte. * If data, trim to stay within window, * dropping FIN if necessary. */ ti->ti_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; tcpstat.tcps_rcvpackafterwin++; tcpstat.tcps_rcvbyteafterwin += todrop; } tp->snd_wl1 = ti->ti_seq - 1; tp->rcv_up = ti->ti_seq; /* * Client side of transaction: already sent SYN and data. * If the remote host used T/TCP to validate the SYN, * our data will be ACK'd; if so, enter normal data segment * processing in the middle of step 5, ack processing. * Otherwise, goto step 6. */ if (tiflags & TH_ACK) goto process_ACK; goto step6; /* * If the state is LAST_ACK or CLOSING or TIME_WAIT: * if segment contains a SYN and CC [not CC.NEW] option: * if state == TIME_WAIT and connection duration > MSL, * drop packet and send RST; * * if SEG.CC > CCrecv then is new SYN, and can implicitly * ack the FIN (and data) in retransmission queue. * Complete close and delete TCPCB. Then reprocess * segment, hoping to find new TCPCB in LISTEN state; * * else must be old SYN; drop it. * else do normal processing. */ case TCPS_LAST_ACK: case TCPS_CLOSING: case TCPS_TIME_WAIT: if ((tiflags & TH_SYN) && (to.to_flag & TOF_CC) && tp->cc_recv != 0) { if (tp->t_state == TCPS_TIME_WAIT && tp->t_duration > TCPTV_MSL) goto dropwithreset; if (CC_GT(to.to_cc, tp->cc_recv)) { tp = tcp_close(tp); goto findpcb; } else goto drop; } break; /* continue normal processing */ } /* * States other than LISTEN or SYN_SENT. * First check timestamp, if present. * Then check the connection count, if present. * Then check that at least some bytes of segment are within * receive window. If segment begins before rcv_nxt, * drop leading data (and SYN); if nothing left, just ack. * * RFC 1323 PAWS: If we have a timestamp reply on this segment * and it's less than ts_recent, drop it. */ if ((to.to_flag & TOF_TS) != 0 && (tiflags & TH_RST) == 0 && tp->ts_recent && TSTMP_LT(to.to_tsval, tp->ts_recent)) { /* Check to see if ts_recent is over 24 days old. */ if ((int)(tcp_now - tp->ts_recent_age) > TCP_PAWS_IDLE) { /* * Invalidate ts_recent. If this segment updates * ts_recent, the age will be reset later and ts_recent * will get a valid value. If it does not, setting * ts_recent to zero will at least satisfy the * requirement that zero be placed in the timestamp * echo reply when ts_recent isn't valid. The * age isn't reset until we get a valid ts_recent * because we don't want out-of-order segments to be * dropped when ts_recent is old. */ tp->ts_recent = 0; } else { tcpstat.tcps_rcvduppack++; tcpstat.tcps_rcvdupbyte += ti->ti_len; tcpstat.tcps_pawsdrop++; goto dropafterack; } } /* * T/TCP mechanism * If T/TCP was negotiated and the segment doesn't have CC, * or if it's CC is wrong then drop the segment. * RST segments do not have to comply with this. */ if ((tp->t_flags & (TF_REQ_CC|TF_RCVD_CC)) == (TF_REQ_CC|TF_RCVD_CC) && ((to.to_flag & TOF_CC) == 0 || tp->cc_recv != to.to_cc) && (tiflags & TH_RST) == 0) goto dropafterack; todrop = tp->rcv_nxt - ti->ti_seq; if (todrop > 0) { if (tiflags & TH_SYN) { tiflags &= ~TH_SYN; ti->ti_seq++; if (ti->ti_urp > 1) ti->ti_urp--; else tiflags &= ~TH_URG; todrop--; } /* * Following if statement from Stevens, vol. 2, p. 960. */ if (todrop > ti->ti_len || (todrop == ti->ti_len && (tiflags & TH_FIN) == 0)) { /* * Any valid FIN must be to the left of the window. * At this point the FIN must be a duplicate or out * of sequence; drop it. */ tiflags &= ~TH_FIN; /* * Send an ACK to resynchronize and drop any data. * But keep on processing for RST or ACK. */ tp->t_flags |= TF_ACKNOW; todrop = ti->ti_len; tcpstat.tcps_rcvduppack++; tcpstat.tcps_rcvdupbyte += todrop; } 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 new data are received on a connection after the * user processes are gone, then RST the other end. */ if ((so->so_state & SS_NOFDREF) && tp->t_state > TCPS_CLOSE_WAIT && ti->ti_len) { tp = tcp_close(tp); tcpstat.tcps_rcvafterclose++; goto dropwithreset; } /* * If segment ends after window, drop trailing data * (and PUSH and FIN); if nothing left, just ACK. */
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?