void tcp_slowtmr(void) { struct tcp_pcb *pcb, *pcb2, *prev; u32_t eff_wnd; u8_t pcb_remove; /* flag if a PCB should be removed */ err_t err; err = ERR_OK; ++tcp_ticks; /* Steps through all of the active PCBs. */ prev = NULL; pcb = tcp_active_pcbs; if (pcb == NULL) LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: no active pcbs\n")); while (pcb != NULL) { LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: processing active pcb\n")); LWIP_ASSERT("tcp_slowtmr: active pcb->state != CLOSED\n", pcb->state != CLOSED); LWIP_ASSERT("tcp_slowtmr: active pcb->state != LISTEN\n", pcb->state != LISTEN); LWIP_ASSERT("tcp_slowtmr: active pcb->state != TIME-WAIT\n", pcb->state != TIME_WAIT); pcb_remove = 0; if (pcb->state == SYN_SENT && pcb->nrtx == TCP_SYNMAXRTX) { ++pcb_remove; LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: max SYN retries reached\n")); } else if (pcb->nrtx == TCP_MAXRTX) { ++pcb_remove; LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: max DATA retries reached\n")); } else { ++pcb->rtime; if (pcb->unacked != NULL && pcb->rtime >= pcb->rto) { /* Time for a retransmission. */ LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_slowtmr: rtime %u pcb->rto %u\n", pcb->rtime, pcb->rto)); /* Double retransmission time-out unless we are trying to * connect to somebody (i.e., we are in SYN_SENT). */ if (pcb->state != SYN_SENT) { pcb->rto = ((pcb->sa >> 3) + pcb->sv) << tcp_backoff[pcb->nrtx]; } tcp_rexmit(pcb); /* Reduce congestion window and ssthresh. */ eff_wnd = LWIP_MIN(pcb->cwnd, pcb->snd_wnd); pcb->ssthresh = eff_wnd >> 1; if (pcb->ssthresh < pcb->mss) { pcb->ssthresh = pcb->mss * 2; } pcb->cwnd = pcb->mss; LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_slowtmr: cwnd %u ssthresh %u\n", pcb->cwnd, pcb->ssthresh)); } }
/** * Handle retransmission after three dupacks received * * @param pcb the tcp_pcb for which to retransmit the first unacked segment */ void tcp_rexmit_fast(struct tcp_pcb *pcb) { if (pcb->unacked != NULL && !(pcb->flags & TF_INFR)) { /* This is fast retransmit. Retransmit the first unacked segment. */ LWIP_DEBUGF(TCP_FR_DEBUG, ("tcp_receive: dupacks %"U16_F" (%"U32_F "), fast retransmit %"U32_F"\n", (u16_t)pcb->dupacks, pcb->lastack, pcb->unacked->seqno)); tcp_rexmit(pcb); #if TCP_CC_ALGO_MOD cc_cong_signal(pcb, CC_NDUPACK); #else /* Set ssthresh to half of the minimum of the current * cwnd and the advertised window */ if (pcb->cwnd > pcb->snd_wnd) { pcb->ssthresh = pcb->snd_wnd / 2; } else { pcb->ssthresh = pcb->cwnd / 2; } /* The minimum value for ssthresh should be 2 MSS */ if (pcb->ssthresh < (2U * pcb->mss)) { LWIP_DEBUGF(TCP_FR_DEBUG, ("tcp_receive: The minimum value for ssthresh %"U16_F " should be min 2 mss %"U16_F"...\n", pcb->ssthresh, 2*pcb->mss)); pcb->ssthresh = 2*pcb->mss; } pcb->cwnd = pcb->ssthresh + 3 * pcb->mss; #endif pcb->flags |= TF_INFR; } }
void tcp_appsend(FAR struct net_driver_s *dev, FAR struct tcp_conn_s *conn, uint16_t result) { uint8_t hdrlen; /* Handle the result based on the application response */ nllvdbg("result: %04x d_sndlen: %d conn->unacked: %d\n", result, dev->d_sndlen, conn->unacked); /* Get the IP header length associated with the IP domain configured for * this TCP connection. */ #ifdef CONFIG_NET_IPv4 #ifdef CONFIG_NET_IPv6 if (conn->domain == PF_INET) #endif { DEBUGASSERT(IFF_IS_IPv4(dev->d_flags)); hdrlen = IPv4TCP_HDRLEN; } #endif /* CONFIG_NET_IPv4 */ #ifdef CONFIG_NET_IPv6 #ifdef CONFIG_NET_IPv4 else #endif { DEBUGASSERT(IFF_IS_IPv6(dev->d_flags)); hdrlen = IPv6TCP_HDRLEN; } #endif /* CONFIG_NET_IPv6 */ /* Check for connection aborted */ if ((result & TCP_ABORT) != 0) { dev->d_sndlen = 0; conn->tcpstateflags = TCP_CLOSED; nllvdbg("TCP state: TCP_CLOSED\n"); tcp_send(dev, conn, TCP_RST | TCP_ACK, hdrlen); } /* Check for connection closed */ else if ((result & TCP_CLOSE) != 0) { conn->tcpstateflags = TCP_FIN_WAIT_1; conn->unacked = 1; conn->nrtx = 0; nllvdbg("TCP state: TCP_FIN_WAIT_1\n"); dev->d_sndlen = 0; tcp_send(dev, conn, TCP_FIN | TCP_ACK, hdrlen); } /* None of the above */ else { #ifdef CONFIG_NET_TCP_WRITE_BUFFERS DEBUGASSERT(dev->d_sndlen >= 0 && dev->d_sndlen <= conn->mss); #else /* If d_sndlen > 0, the application has data to be sent. */ if (dev->d_sndlen > 0) { /* Remember how much data we send out now so that we know * when everything has been acknowledged. Just increment the amount * of data sent. This will be needed in sequence number calculations * and we know that this is not a re-transmission. Retransmissions * do not go through this path. */ conn->unacked += dev->d_sndlen; /* The application cannot send more than what is allowed by the * MSS (the minumum of the MSS and the available window). */ DEBUGASSERT(dev->d_sndlen <= conn->mss); } conn->nrtx = 0; #endif /* Then handle the rest of the operation just as for the rexmit case */ tcp_rexmit(dev, conn, result); } }
/** * Called by tcp_process. Checks if the given segment is an ACK for outstanding * data, and if so frees the memory of the buffered data. Next, is places the * segment on any of the receive queues (pcb->recved or pcb->ooseq). If the segment * is buffered, the pbuf is referenced by pbuf_ref so that it will not be freed until * i it has been removed from the buffer. * * If the incoming segment constitutes an ACK for a segment that was used for RTT * estimation, the RTT is estimated here as well. * * Called from tcp_process(). * * @return 1 if the incoming segment is the next in sequence, 0 if not */ static u8_t tcp_receive(struct tcp_pcb *pcb) { struct tcp_seg *next; #if TCP_QUEUE_OOSEQ struct tcp_seg *prev, *cseg; #endif struct pbuf *p; s32_t off; s16_t m; u32_t right_wnd_edge; u16_t new_tot_len; u8_t accepted_inseq = 0; if (flags & TCP_ACK) { right_wnd_edge = pcb->snd_wnd + pcb->snd_wl1; /* Update window. */ if (TCP_SEQ_LT(pcb->snd_wl1, seqno) || (pcb->snd_wl1 == seqno && TCP_SEQ_LT(pcb->snd_wl2, ackno)) || (pcb->snd_wl2 == ackno && tcphdr->wnd > pcb->snd_wnd)) { pcb->snd_wnd = tcphdr->wnd; pcb->snd_wl1 = seqno; pcb->snd_wl2 = ackno; if (pcb->snd_wnd > 0 && pcb->persist_backoff > 0) { pcb->persist_backoff = 0; } LWIP_DEBUGF(TCP_WND_DEBUG, ("tcp_receive: window update %"U16_F"\n", pcb->snd_wnd)); #if TCP_WND_DEBUG } else { if (pcb->snd_wnd != tcphdr->wnd) { LWIP_DEBUGF(TCP_WND_DEBUG, ("tcp_receive: no window update lastack %"U32_F" snd_max %"U32_F" ackno %"U32_F" wl1 %"U32_F" seqno %"U32_F" wl2 %"U32_F"\n", pcb->lastack, pcb->snd_max, ackno, pcb->snd_wl1, seqno, pcb->snd_wl2)); } #endif /* TCP_WND_DEBUG */ } if (pcb->lastack == ackno) { pcb->acked = 0; if (pcb->snd_wl1 + pcb->snd_wnd == right_wnd_edge){ ++pcb->dupacks; if (pcb->dupacks >= 3 && pcb->unacked != NULL) { if (!(pcb->flags & TF_INFR)) { /* This is fast retransmit. Retransmit the first unacked segment. */ LWIP_DEBUGF(TCP_FR_DEBUG, ("tcp_receive: dupacks %"U16_F" (%"U32_F"), fast retransmit %"U32_F"\n", (u16_t)pcb->dupacks, pcb->lastack, ntohl(pcb->unacked->tcphdr->seqno))); tcp_rexmit(pcb); /* Set ssthresh to max (FlightSize / 2, 2*SMSS) */ /*pcb->ssthresh = LWIP_MAX((pcb->snd_max - pcb->lastack) / 2, 2 * pcb->mss);*/ /* Set ssthresh to half of the minimum of the current cwnd and the advertised window */ if (pcb->cwnd > pcb->snd_wnd) pcb->ssthresh = pcb->snd_wnd / 2; else pcb->ssthresh = pcb->cwnd / 2; /* The minimum value for ssthresh should be 2 MSS */ if (pcb->ssthresh < 2*pcb->mss) { LWIP_DEBUGF(TCP_FR_DEBUG, ("tcp_receive: The minimum value for ssthresh %"U16_F" should be min 2 mss %"U16_F"...\n", pcb->ssthresh, 2*pcb->mss)); pcb->ssthresh = 2*pcb->mss; } pcb->cwnd = pcb->ssthresh + 3 * pcb->mss; pcb->flags |= TF_INFR; } else { /* Inflate the congestion window, but not if it means that the value overflows. */ if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) { pcb->cwnd += pcb->mss; } } } } else { LWIP_DEBUGF(TCP_FR_DEBUG, ("tcp_receive: dupack averted %"U32_F" %"U32_F"\n", pcb->snd_wl1 + pcb->snd_wnd, right_wnd_edge)); } } else if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_max)){ /* We come here when the ACK acknowledges new data. */ /* Reset the "IN Fast Retransmit" flag, since we are no longer in fast retransmit. Also reset the congestion window to the slow start threshold. */ if (pcb->flags & TF_INFR) { pcb->flags &= ~TF_INFR; pcb->cwnd = pcb->ssthresh; } /* Reset the number of retransmissions. */ pcb->nrtx = 0; /* Reset the retransmission time-out. */ pcb->rto = (pcb->sa >> 3) + pcb->sv; /* Update the send buffer space. Diff between the two can never exceed 64K? */ pcb->acked = (u16_t)(ackno - pcb->lastack); pcb->snd_buf += pcb->acked; /* Reset the fast retransmit variables. */ pcb->dupacks = 0; pcb->lastack = ackno; /* Update the congestion control variables (cwnd and ssthresh). */ if (pcb->state >= ESTABLISHED) { if (pcb->cwnd < pcb->ssthresh) { if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) { pcb->cwnd += pcb->mss; } LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: slow start cwnd %"U16_F"\n", pcb->cwnd)); } else { u16_t new_cwnd = (pcb->cwnd + pcb->mss * pcb->mss / pcb->cwnd); if (new_cwnd > pcb->cwnd) { pcb->cwnd = new_cwnd; } LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: congestion avoidance cwnd %"U16_F"\n", pcb->cwnd)); } } LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_receive: ACK for %"U32_F", unacked->seqno %"U32_F":%"U32_F"\n", ackno, pcb->unacked != NULL? ntohl(pcb->unacked->tcphdr->seqno): 0, pcb->unacked != NULL? ntohl(pcb->unacked->tcphdr->seqno) + TCP_TCPLEN(pcb->unacked): 0)); /* Remove segment from the unacknowledged list if the incoming ACK acknowlegdes them. */ while (pcb->unacked != NULL && TCP_SEQ_LEQ(ntohl(pcb->unacked->tcphdr->seqno) + TCP_TCPLEN(pcb->unacked), ackno)) { LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_receive: removing %"U32_F":%"U32_F" from pcb->unacked\n", ntohl(pcb->unacked->tcphdr->seqno), ntohl(pcb->unacked->tcphdr->seqno) + TCP_TCPLEN(pcb->unacked))); next = pcb->unacked; pcb->unacked = pcb->unacked->next; LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_receive: queuelen %"U16_F" ... ", (u16_t)pcb->snd_queuelen)); LWIP_ASSERT("pcb->snd_queuelen >= pbuf_clen(next->p)", (pcb->snd_queuelen >= pbuf_clen(next->p))); pcb->snd_queuelen -= pbuf_clen(next->p); tcp_seg_free(next); LWIP_DEBUGF(TCP_QLEN_DEBUG, ("%"U16_F" (after freeing unacked)\n", (u16_t)pcb->snd_queuelen)); if (pcb->snd_queuelen != 0) { LWIP_ASSERT("tcp_receive: valid queue length", pcb->unacked != NULL || pcb->unsent != NULL); } } /* If there's nothing left to acknowledge, stop the retransmit timer, otherwise reset it to start again */ if(pcb->unacked == NULL) pcb->rtime = -1; else pcb->rtime = 0; pcb->polltmr = 0; } else {
void tcp_timer(FAR struct net_driver_s *dev, FAR struct tcp_conn_s *conn, int hsec) { uint16_t result; uint8_t hdrlen; /* Set up for the callback. We can't know in advance if the application * is going to send a IPv4 or an IPv6 packet, so this setup may not * actually be used. Furthermore, the TCP logic is required to call * tcp_ipv4_select() or tcp_ipv6_select() prior to sending any packets. * We will try to set the correct value here basic on the binding of * the connection. */ #ifdef CONFIG_NET_IPv4 #ifdef CONFIG_NET_IPv6 if (conn->domain == PF_INET) #endif { hdrlen = IPv4TCP_HDRLEN; tcp_ipv4_select(dev); } #endif /* CONFIG_NET_IPv4 */ #ifdef CONFIG_NET_IPv6 #ifdef CONFIG_NET_IPv4 else #endif { hdrlen = IPv6TCP_HDRLEN; tcp_ipv6_select(dev); } #endif /* CONFIG_NET_IPv6 */ /* Increase the TCP sequence number */ tcp_nextsequence(); /* Reset the length variables. */ dev->d_len = 0; dev->d_sndlen = 0; /* Check if the connection is in a state in which we simply wait * for the connection to time out. If so, we increase the * connection's timer and remove the connection if it times * out. */ if (conn->tcpstateflags == TCP_TIME_WAIT || conn->tcpstateflags == TCP_FIN_WAIT_2) { unsigned int newtimer; /* Increment the connection timer */ newtimer = (unsigned int)conn->timer + hsec; /* Check if the timer exceeds the timeout value */ if (newtimer >= TCP_TIME_WAIT_TIMEOUT) { /* Set the timer to the maximum value */ conn->timer = TCP_TIME_WAIT_TIMEOUT; /* The TCP connection was established and, hence, should be bound * to a device. Make sure that the polling device is the one that * we are bound to. * * If not, then we will catch the timeout on the next poll from * the correct device. */ DEBUGASSERT(conn->dev != NULL); if (dev != conn->dev) { ninfo("TCP: TCP_CLOSED pending\n"); } else { conn->tcpstateflags = TCP_CLOSED; /* Notify upper layers about the timeout */ result = tcp_callback(dev, conn, TCP_TIMEDOUT); ninfo("TCP state: TCP_CLOSED\n"); } } else { /* No timeout. Just update the incremented timer */ conn->timer = newtimer; } } else if (conn->tcpstateflags != TCP_CLOSED) { /* If the connection has outstanding data, we increase the connection's * timer and see if it has reached the RTO value in which case we * retransmit. */ if (conn->unacked > 0) { /* The connection has outstanding data */ if (conn->timer > hsec) { /* Will not yet decrement to zero */ conn->timer -= hsec; } else { /* Will decrement to zero */ conn->timer = 0; /* The TCP is connected and, hence, should be bound to a * device. Make sure that the polling device is the one that * we are bound to. * * If not, then we will catch the timeout on the next poll * from the correct device. */ DEBUGASSERT(conn->dev != NULL); if (dev != conn->dev) { ninfo("TCP: TCP_CLOSED pending\n"); goto done; } /* Check for a timeout on connection in the TCP_SYN_RCVD state. * On such timeouts, we would normally resend the SYNACK until * the ACK is received, completing the 3-way handshake. But if * the retry count elapsed, then we must assume that no ACK is * forthcoming and terminate the attempted connection. */ if (conn->tcpstateflags == TCP_SYN_RCVD && conn->nrtx >= TCP_MAXSYNRTX) { FAR struct tcp_conn_s *listener; conn->tcpstateflags = TCP_CLOSED; ninfo("TCP state: TCP_SYN_RCVD->TCP_CLOSED\n"); /* Find the listener for this connection. */ #if defined(CONFIG_NET_IPv4) && defined(CONFIG_NET_IPv6) listener = tcp_findlistener(conn->lport, conn->domain); #else listener = tcp_findlistener(conn->lport); #endif if (listener != NULL) { /* We call tcp_callback() for the connection with * TCP_TIMEDOUT to inform the listener that the * connection has timed out. */ result = tcp_callback(dev, listener, TCP_TIMEDOUT); } /* We also send a reset packet to the remote host. */ tcp_send(dev, conn, TCP_RST | TCP_ACK, hdrlen); /* Finally, we must free this TCP connection structure */ tcp_free(conn); goto done; } /* Otherwise, check for a timeout on an established connection. * If the retry count is exceeded in this case, we should * close the connection. */ else if ( #ifdef CONFIG_NET_TCP_WRITE_BUFFERS conn->expired > 0 || #else conn->nrtx >= TCP_MAXRTX || #endif (conn->tcpstateflags == TCP_SYN_SENT && conn->nrtx >= TCP_MAXSYNRTX) ) { conn->tcpstateflags = TCP_CLOSED; ninfo("TCP state: TCP_CLOSED\n"); /* We call tcp_callback() with TCP_TIMEDOUT to * inform the application that the connection has * timed out. */ result = tcp_callback(dev, conn, TCP_TIMEDOUT); /* We also send a reset packet to the remote host. */ tcp_send(dev, conn, TCP_RST | TCP_ACK, hdrlen); goto done; } /* Exponential backoff. */ conn->timer = TCP_RTO << (conn->nrtx > 4 ? 4: conn->nrtx); (conn->nrtx)++; /* Ok, so we need to retransmit. We do this differently * depending on which state we are in. In ESTABLISHED, we * call upon the application so that it may prepare the * data for the retransmit. In SYN_RCVD, we resend the * SYNACK that we sent earlier and in LAST_ACK we have to * retransmit our FINACK. */ #ifdef CONFIG_NET_STATISTICS g_netstats.tcp.rexmit++; #endif switch (conn->tcpstateflags & TCP_STATE_MASK) { case TCP_SYN_RCVD: /* In the SYN_RCVD state, we should retransmit our * SYNACK. */ tcp_ack(dev, conn, TCP_ACK | TCP_SYN); goto done; case TCP_SYN_SENT: /* In the SYN_SENT state, we retransmit out SYN. */ tcp_ack(dev, conn, TCP_SYN); goto done; case TCP_ESTABLISHED: /* In the ESTABLISHED state, we call upon the application * to do the actual retransmit after which we jump into * the code for sending out the packet. */ result = tcp_callback(dev, conn, TCP_REXMIT); tcp_rexmit(dev, conn, result); goto done; case TCP_FIN_WAIT_1: case TCP_CLOSING: case TCP_LAST_ACK: /* In all these states we should retransmit a FINACK. */ tcp_send(dev, conn, TCP_FIN | TCP_ACK, hdrlen); goto done; } } } /* The connection does not have outstanding data. Check if the TCP * connection has been established. */ else if ((conn->tcpstateflags & TCP_STATE_MASK) == TCP_ESTABLISHED) { /* The TCP connection is established and, hence, should be bound * to a device. Make sure that the polling device is the one that * we are bound to. */ DEBUGASSERT(conn->dev != NULL); if (dev == conn->dev) { #ifdef CONFIG_NET_TCP_KEEPALIVE /* Is this an established connected with KeepAlive enabled? */ if (conn->keepalive) { socktimeo_t timeo; uint32_t saveseq; /* If this is the first probe, then the keepstart time is * the time that the last ACK or data was received from the * remote. * * On subsequent retries, keepstart is the time that the * last probe was sent. */ if (conn->keepretries > 0) { timeo = (socktimeo_t)conn->keepintvl; } else { timeo = (socktimeo_t)conn->keepidle; } /* Yes... has the idle period elapsed with no data or ACK * received from the remote peer? */ if (net_timeo(conn->keeptime, timeo)) { /* Yes.. Has the retry count expired? */ if (conn->keepretries >= conn->keepcnt) { /* Yes... stop the network monitor, closing the connection and all sockets * associated with the connection. */ tcp_stop_monitor(conn, TCP_ABORT); } else { unsigned int tcpiplen; /* No.. we need to send another probe. * * Get the size of the IP header and the TCP header. */ #ifdef CONFIG_NET_IPv4 #ifdef CONFIG_NET_IPv6 if (conn->domain == PF_INET) #endif { tcpiplen = IPv4_HDRLEN + TCP_HDRLEN; } #endif #ifdef CONFIG_NET_IPv6 #ifdef CONFIG_NET_IPv4 else #endif { tcpiplen = IPv6_HDRLEN + TCP_HDRLEN; } #endif /* And send the probe (along with a garbage byte). * The packet we sned must have these properties: * * - TCP_ACK flag (only) is set. * - Sequence number is the sequence number of * previously ACKed data, i.e., the expected * sequence number minus one. * - The data payload is one or two bytes. * * tcp_send() will send the TCP sequence number as * conn->sndseq. Rather than creating a new * interface, we spoof tcp_end() here: */ saveseq = tcp_getsequence(conn->sndseq); tcp_setsequence(conn->sndseq, saveseq - 1); tcp_send(dev, conn, TCP_ACK, tcpiplen + 1); tcp_setsequence(conn->sndseq, saveseq); /* Increment the number of un-ACKed bytes due to the dummy * byte that we just sent. */ conn->unacked++; #ifdef CONFIG_NET_TCP_WRITE_BUFFERS /* Increment the un-ACKed sequence number */ conn->sndseq_max++; #endif /* Update for the next probe */ conn->keeptime = clock_systimer(); conn->keepretries++; } goto done; } } #endif /* There was no need for a retransmission and there was no * need to probe the remote peer. We poll the application for * new outgoing data. */ result = tcp_callback(dev, conn, TCP_POLL); tcp_appsend(dev, conn, result); goto done; } } } /* Nothing to be done */ dev->d_len = 0; done: return; }
void tcp_timer(FAR struct net_driver_s *dev, FAR struct tcp_conn_s *conn, int hsec) { uint32_t result; uint8_t hdrlen; /* Set up for the callback. We can't know in advance if the application * is going to send a IPv4 or an IPv6 packet, so this setup may not * actually be used. Furthermore, the TCP logic is required to call * tcp_ipv4_select() or tcp_ipv6_select() prior to sending any packets. * We will try to set the correct value here basic on the binding of * the connection. */ #ifdef CONFIG_NET_IPv4 #ifdef CONFIG_NET_IPv6 if (conn->domain == PF_INET) #endif { hdrlen = IPv4TCP_HDRLEN; tcp_ipv4_select(dev); } #endif /* CONFIG_NET_IPv4 */ #ifdef CONFIG_NET_IPv6 #ifdef CONFIG_NET_IPv4 else #endif { hdrlen = IPv6TCP_HDRLEN; tcp_ipv6_select(dev); } #endif /* CONFIG_NET_IPv6 */ /* Increase the TCP sequence number */ tcp_nextsequence(); /* Reset the length variables. */ dev->d_len = 0; dev->d_sndlen = 0; /* Check if the connection is in a state in which we simply wait * for the connection to time out. If so, we increase the * connection's timer and remove the connection if it times * out. */ if (conn->tcpstateflags == TCP_TIME_WAIT || conn->tcpstateflags == TCP_FIN_WAIT_2) { unsigned int newtimer; /* Increment the connection timer */ newtimer = (unsigned int)conn->timer + hsec; /* Check if the timer exceeds the timeout value */ if (newtimer >= TCP_TIME_WAIT_TIMEOUT) { /* Set the timer to the maximum value */ conn->timer = TCP_TIME_WAIT_TIMEOUT; #ifdef CONFIG_NETDEV_MULTINIC /* The TCP connection was established and, hence, should be bound * to a device. Make sure that the polling device is the one that * we are bound to. * * If not, then we will catch the timeout on the next poll from * the correct device. */ DEBUGASSERT(conn->dev != NULL); if (dev != conn->dev) { nllvdbg("TCP: TCP_CLOSED pending\n"); } else #endif { conn->tcpstateflags = TCP_CLOSED; /* Notify upper layers about the timeout */ result = tcp_callback(dev, conn, TCP_TIMEDOUT); nllvdbg("TCP state: TCP_CLOSED\n"); } } else { /* No timeout. Just update the incremented timer */ conn->timer = newtimer; } } else if (conn->tcpstateflags != TCP_CLOSED) { /* If the connection has outstanding data, we increase the connection's * timer and see if it has reached the RTO value in which case we * retransmit. */ if (conn->unacked > 0) { /* The connection has outstanding data */ if (conn->timer > hsec) { /* Will not yet decrement to zero */ conn->timer -= hsec; } else { /* Will decrement to zero */ conn->timer = 0; #ifdef CONFIG_NETDEV_MULTINIC /* The TCP is connected and, hence, should be bound to a * device. Make sure that the polling device is the one that * we are bound to. * * If not, then we will catch the timeout on the next poll * from the correct device. */ DEBUGASSERT(conn->dev != NULL); if (dev != conn->dev) { nllvdbg("TCP: TCP_CLOSED pending\n"); goto done; } #endif /* Should we close the connection? */ if ( #ifdef CONFIG_NET_TCP_WRITE_BUFFERS conn->expired > 0 || #else conn->nrtx == TCP_MAXRTX || #endif ((conn->tcpstateflags == TCP_SYN_SENT || conn->tcpstateflags == TCP_SYN_RCVD) && conn->nrtx == TCP_MAXSYNRTX) ) { conn->tcpstateflags = TCP_CLOSED; nllvdbg("TCP state: TCP_CLOSED\n"); /* We call tcp_callback() with TCP_TIMEDOUT to * inform the application that the connection has * timed out. */ result = tcp_callback(dev, conn, TCP_TIMEDOUT); /* We also send a reset packet to the remote host. */ tcp_send(dev, conn, TCP_RST | TCP_ACK, hdrlen); goto done; } /* Exponential backoff. */ conn->timer = TCP_RTO << (conn->nrtx > 4 ? 4: conn->nrtx); (conn->nrtx)++; /* Ok, so we need to retransmit. We do this differently * depending on which state we are in. In ESTABLISHED, we * call upon the application so that it may prepare the * data for the retransmit. In SYN_RCVD, we resend the * SYNACK that we sent earlier and in LAST_ACK we have to * retransmit our FINACK. */ #ifdef CONFIG_NET_STATISTICS g_netstats.tcp.rexmit++; #endif switch (conn->tcpstateflags & TCP_STATE_MASK) { case TCP_SYN_RCVD: /* In the SYN_RCVD state, we should retransmit our * SYNACK. */ tcp_ack(dev, conn, TCP_ACK | TCP_SYN); goto done; case TCP_SYN_SENT: /* In the SYN_SENT state, we retransmit out SYN. */ tcp_ack(dev, conn, TCP_SYN); goto done; case TCP_ESTABLISHED: /* In the ESTABLISHED state, we call upon the application * to do the actual retransmit after which we jump into * the code for sending out the packet. */ result = tcp_callback(dev, conn, TCP_REXMIT); tcp_rexmit(dev, conn, result); goto done; case TCP_FIN_WAIT_1: case TCP_CLOSING: case TCP_LAST_ACK: /* In all these states we should retransmit a FINACK. */ tcp_send(dev, conn, TCP_FIN | TCP_ACK, hdrlen); goto done; } } } /* The connection does not have outstanding data. Check if the TCP * connection has been established. */ else if ((conn->tcpstateflags & TCP_STATE_MASK) == TCP_ESTABLISHED) { /* If there was no need for a retransmission, we poll the * application for new data. */ #ifdef CONFIG_NETDEV_MULTINIC /* The TCP connection is established and, hence, should be bound * to a device. Make sure that the polling device is the one that * we are bound to. */ DEBUGASSERT(conn->dev != NULL); if (dev == conn->dev) #endif { result = tcp_callback(dev, conn, TCP_POLL); tcp_appsend(dev, conn, result); goto done; } } } /* Nothing to be done */ dev->d_len = 0; done: return; }
/** * Implements the TCP state machine. Called by tcp_input. In some * states tcp_receive() is called to receive data. The tcp_seg * argument will be freed by the caller (tcp_input()) unless the * recv_data pointer in the pcb is set. * * @param pcb the tcp_pcb for which a segment arrived * * @note the segment which arrived is saved in global variables, therefore only the pcb * involved is passed as a parameter to this function */ static err_t tcp_process(struct tcp_pcb *pcb) { struct tcp_seg *rseg; u8_t acceptable = 0; err_t err; err = ERR_OK; /* Process incoming RST segments. */ if (flags & TCP_RST) { /* First, determine if the reset is acceptable. */ if (pcb->state == SYN_SENT) { if (ackno == pcb->snd_nxt) { acceptable = 1; } } else { if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt+pcb->rcv_wnd)) { acceptable = 1; } } if (acceptable) { LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_process: Connection RESET\n")); LWIP_ASSERT("tcp_input: pcb->state != CLOSED", pcb->state != CLOSED); recv_flags |= TF_RESET; pcb->flags &= ~TF_ACK_DELAY; return ERR_RST; } else { LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_process: unacceptable reset seqno %"U32_F" rcv_nxt %"U32_F"\n", seqno, pcb->rcv_nxt)); LWIP_DEBUGF(TCP_DEBUG, ("tcp_process: unacceptable reset seqno %"U32_F" rcv_nxt %"U32_F"\n", seqno, pcb->rcv_nxt)); return ERR_OK; } } if ((flags & TCP_SYN) && (pcb->state != SYN_SENT && pcb->state != SYN_RCVD)) { /* Cope with new connection attempt after remote end crashed */ tcp_ack_now(pcb); return ERR_OK; } /* Update the PCB (in)activity timer. */ pcb->tmr = tcp_ticks; pcb->keep_cnt_sent = 0; tcp_parseopt(pcb); /* Do different things depending on the TCP state. */ switch (pcb->state) { case SYN_SENT: LWIP_DEBUGF(TCP_INPUT_DEBUG, ("SYN-SENT: ackno %"U32_F" pcb->snd_nxt %"U32_F" unacked %"U32_F"\n", ackno, pcb->snd_nxt, ntohl(pcb->unacked->tcphdr->seqno))); /* received SYN ACK with expected sequence number? */ if ((flags & TCP_ACK) && (flags & TCP_SYN) && ackno == ntohl(pcb->unacked->tcphdr->seqno) + 1) { pcb->snd_buf++; pcb->rcv_nxt = seqno + 1; pcb->rcv_ann_right_edge = pcb->rcv_nxt; pcb->lastack = ackno; pcb->snd_wnd = tcphdr->wnd; pcb->snd_wl1 = seqno - 1; /* initialise to seqno - 1 to force window update */ pcb->state = ESTABLISHED; #if TCP_CALCULATE_EFF_SEND_MSS pcb->mss = tcp_eff_send_mss(pcb->mss, &(pcb->remote_ip)); #endif /* TCP_CALCULATE_EFF_SEND_MSS */ /* Set ssthresh again after changing pcb->mss (already set in tcp_connect * but for the default value of pcb->mss) */ pcb->ssthresh = pcb->mss * 10; pcb->cwnd = ((pcb->cwnd == 1) ? (pcb->mss * 2) : pcb->mss); LWIP_ASSERT("pcb->snd_queuelen > 0", (pcb->snd_queuelen > 0)); --pcb->snd_queuelen; LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_process: SYN-SENT --queuelen %"U16_F"\n", (u16_t)pcb->snd_queuelen)); rseg = pcb->unacked; pcb->unacked = rseg->next; /* If there's nothing left to acknowledge, stop the retransmit timer, otherwise reset it to start again */ if(pcb->unacked == NULL) pcb->rtime = -1; else { pcb->rtime = 0; pcb->nrtx = 0; } tcp_seg_free(rseg); /* Call the user specified function to call when sucessfully * connected. */ TCP_EVENT_CONNECTED(pcb, ERR_OK, err); tcp_ack_now(pcb); } /* received ACK? possibly a half-open connection */ else if (flags & TCP_ACK) { /* send a RST to bring the other side in a non-synchronized state. */ tcp_rst(ackno, seqno + tcplen, &(iphdr->dest), &(iphdr->src), tcphdr->dest, tcphdr->src); } break; case SYN_RCVD: if (flags & TCP_ACK) { /* expected ACK number? */ if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)) { u16_t old_cwnd; pcb->state = ESTABLISHED; LWIP_DEBUGF(TCP_DEBUG, ("TCP connection established %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest)); #if LWIP_CALLBACK_API LWIP_ASSERT("pcb->accept != NULL", pcb->accept != NULL); #endif /* Call the accept function. */ TCP_EVENT_ACCEPT(pcb, ERR_OK, err); if (err != ERR_OK) { /* If the accept function returns with an error, we abort * the connection. */ tcp_abort(pcb); return ERR_ABRT; } old_cwnd = pcb->cwnd; /* If there was any data contained within this ACK, * we'd better pass it on to the application as well. */ tcp_receive(pcb); /* Prevent ACK for SYN to generate a sent event */ if (pcb->acked != 0) { pcb->acked--; } pcb->cwnd = ((old_cwnd == 1) ? (pcb->mss * 2) : pcb->mss); if (recv_flags & TF_GOT_FIN) { tcp_ack_now(pcb); pcb->state = CLOSE_WAIT; } } /* incorrect ACK number */ else { /* send RST */ tcp_rst(ackno, seqno + tcplen, &(iphdr->dest), &(iphdr->src), tcphdr->dest, tcphdr->src); } } else if ((flags & TCP_SYN) && (seqno == pcb->rcv_nxt - 1)) { /* Looks like another copy of the SYN - retransmit our SYN-ACK */ tcp_rexmit(pcb); } break; case CLOSE_WAIT: /* FALLTHROUGH */ case ESTABLISHED: tcp_receive(pcb); if (recv_flags & TF_GOT_FIN) { /* passive close */ tcp_ack_now(pcb); pcb->state = CLOSE_WAIT; } break; case FIN_WAIT_1: tcp_receive(pcb); if (recv_flags & TF_GOT_FIN) { if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt)) { LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: FIN_WAIT_1 %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest)); tcp_ack_now(pcb); tcp_pcb_purge(pcb); TCP_RMV(&tcp_active_pcbs, pcb); pcb->state = TIME_WAIT; TCP_REG(&tcp_tw_pcbs, pcb); } else { tcp_ack_now(pcb); pcb->state = CLOSING; } } else if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt)) { pcb->state = FIN_WAIT_2; } break; case FIN_WAIT_2: tcp_receive(pcb); if (recv_flags & TF_GOT_FIN) { LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: FIN_WAIT_2 %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest)); tcp_ack_now(pcb); tcp_pcb_purge(pcb); TCP_RMV(&tcp_active_pcbs, pcb); pcb->state = TIME_WAIT; TCP_REG(&tcp_tw_pcbs, pcb); } break; case CLOSING: tcp_receive(pcb); if (flags & TCP_ACK && ackno == pcb->snd_nxt) { LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: CLOSING %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest)); tcp_pcb_purge(pcb); TCP_RMV(&tcp_active_pcbs, pcb); pcb->state = TIME_WAIT; TCP_REG(&tcp_tw_pcbs, pcb); } break; case LAST_ACK: tcp_receive(pcb); if (flags & TCP_ACK && ackno == pcb->snd_nxt) { LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: LAST_ACK %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest)); /* bugfix #21699: don't set pcb->state to CLOSED here or we risk leaking segments */ recv_flags |= TF_CLOSED; } break; default: break; } return ERR_OK; }