/** @file TCP timer related functions. Copyright (c) 2009 - 2010, Intel Corporation. All rights reserved.
Copyright (c) Microsoft Corporation SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "TcpMain.h" UINT32 mTcpTick = 1000; /** Connect timeout handler. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. **/ VOID TcpConnectTimeout ( IN OUT TCP_CB *Tcb ); /** Timeout handler for TCP retransmission timer. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. **/ VOID TcpRexmitTimeout ( IN OUT TCP_CB *Tcb ); /** Timeout handler for window probe timer. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. **/ VOID TcpProbeTimeout ( IN OUT TCP_CB *Tcb ); /** Timeout handler for keepalive timer. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. **/ VOID TcpKeepaliveTimeout ( IN OUT TCP_CB *Tcb ); /** Timeout handler for FIN_WAIT_2 timer. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. **/ VOID TcpFinwait2Timeout ( IN OUT TCP_CB *Tcb ); /** Timeout handler for 2MSL timer. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. **/ VOID Tcp2MSLTimeout ( IN OUT TCP_CB *Tcb ); TCP_TIMER_HANDLER mTcpTimerHandler[TCP_TIMER_NUMBER] = { TcpConnectTimeout, TcpRexmitTimeout, TcpProbeTimeout, TcpKeepaliveTimeout, TcpFinwait2Timeout, Tcp2MSLTimeout, }; /** Close the TCP connection. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. **/ VOID TcpClose ( IN OUT TCP_CB *Tcb ) { NetbufFreeList (&Tcb->SndQue); NetbufFreeList (&Tcb->RcvQue); TcpSetState (Tcb, TCP_CLOSED); } /** Backoff the RTO. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. **/ VOID TcpBackoffRto ( IN OUT TCP_CB *Tcb ) { // // Fold the RTT estimate if too many times, the estimate // may be wrong, fold it. So the next time a valid // measurement is sampled, we can start fresh. // if ((Tcb->LossTimes >= TCP_FOLD_RTT) && (Tcb->SRtt != 0)) { Tcb->RttVar += Tcb->SRtt >> 2; Tcb->SRtt = 0; } Tcb->Rto <<= 1; if (Tcb->Rto < TCP_RTO_MIN) { Tcb->Rto = TCP_RTO_MIN; } else if (Tcb->Rto > TCP_RTO_MAX) { Tcb->Rto = TCP_RTO_MAX; } } /** Connect timeout handler. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. **/ VOID TcpConnectTimeout ( IN OUT TCP_CB *Tcb ) { if (!TCP_CONNECTED (Tcb->State)) { DEBUG ( (DEBUG_ERROR, "TcpConnectTimeout: connection closed because connection timer timeout for TCB %p\n", Tcb) ); if (EFI_ABORTED == Tcb->Sk->SockError) { SOCK_ERROR (Tcb->Sk, EFI_TIMEOUT); } if (TCP_SYN_RCVD == Tcb->State) { DEBUG ( (DEBUG_WARN, "TcpConnectTimeout: send reset because connection timer timeout for TCB %p\n", Tcb) ); TcpResetConnection (Tcb); } TcpClose (Tcb); } } /** Timeout handler for TCP retransmission timer. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. **/ VOID TcpRexmitTimeout ( IN OUT TCP_CB *Tcb ) { UINT32 FlightSize; DEBUG ( (DEBUG_WARN, "TcpRexmitTimeout: transmission timeout for TCB %p\n", Tcb) ); // // Set the congestion window. FlightSize is the // amount of data that has been sent but not // yet ACKed. // FlightSize = TCP_SUB_SEQ (Tcb->SndNxt, Tcb->SndUna); Tcb->Ssthresh = MAX ((UINT32)(2 * Tcb->SndMss), FlightSize / 2); Tcb->CWnd = Tcb->SndMss; Tcb->LossRecover = Tcb->SndNxt; Tcb->LossTimes++; if ((Tcb->LossTimes > Tcb->MaxRexmit) && !TCP_TIMER_ON (Tcb->EnabledTimer, TCP_TIMER_CONNECT)) { DEBUG ( (DEBUG_ERROR, "TcpRexmitTimeout: connection closed because too many timeouts for TCB %p\n", Tcb) ); if (EFI_ABORTED == Tcb->Sk->SockError) { SOCK_ERROR (Tcb->Sk, EFI_TIMEOUT); } TcpClose (Tcb); return; } TcpBackoffRto (Tcb); TcpRetransmit (Tcb, Tcb->SndUna); TcpSetTimer (Tcb, TCP_TIMER_REXMIT, Tcb->Rto); Tcb->CongestState = TCP_CONGEST_LOSS; TCP_CLEAR_FLG (Tcb->CtrlFlag, TCP_CTRL_RTT_ON); } /** Timeout handler for window probe timer. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. **/ VOID TcpProbeTimeout ( IN OUT TCP_CB *Tcb ) { // // This is the timer for sender's SWSA. RFC1122 requires // a timer set for sender's SWSA, and suggest combine it // with window probe timer. If data is sent, don't set // the probe timer, since retransmit timer is on. // if ((TcpDataToSend (Tcb, 1) != 0) && (TcpToSendData (Tcb, 1) > 0)) { ASSERT (TCP_TIMER_ON (Tcb->EnabledTimer, TCP_TIMER_REXMIT) != 0); Tcb->ProbeTimerOn = FALSE; return; } TcpSendZeroProbe (Tcb); TcpSetProbeTimer (Tcb); } /** Timeout handler for keepalive timer. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. **/ VOID TcpKeepaliveTimeout ( IN OUT TCP_CB *Tcb ) { Tcb->KeepAliveProbes++; // // Too many Keep-alive probes, drop the connection // if (Tcb->KeepAliveProbes > Tcb->MaxKeepAlive) { if (EFI_ABORTED == Tcb->Sk->SockError) { SOCK_ERROR (Tcb->Sk, EFI_TIMEOUT); } TcpClose (Tcb); return; } TcpSendZeroProbe (Tcb); TcpSetKeepaliveTimer (Tcb); } /** Timeout handler for FIN_WAIT_2 timer. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. **/ VOID TcpFinwait2Timeout ( IN OUT TCP_CB *Tcb ) { DEBUG ( (DEBUG_WARN, "TcpFinwait2Timeout: connection closed because FIN_WAIT2 timer timeouts for TCB %p\n", Tcb) ); TcpClose (Tcb); } /** Timeout handler for 2MSL timer. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. **/ VOID Tcp2MSLTimeout ( IN OUT TCP_CB *Tcb ) { DEBUG ( (DEBUG_WARN, "Tcp2MSLTimeout: connection closed because TIME_WAIT timer timeouts for TCB %p\n", Tcb) ); TcpClose (Tcb); } /** Update the timer status and the next expire time according to the timers to expire in a specific future time slot. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. **/ VOID TcpUpdateTimer ( IN OUT TCP_CB *Tcb ) { UINT16 Index; // // Don't use a too large value to init NextExpire // since mTcpTick wraps around as sequence no does. // Tcb->NextExpire = TCP_EXPIRE_TIME; TCP_CLEAR_FLG (Tcb->CtrlFlag, TCP_CTRL_TIMER_ON); for (Index = 0; Index < TCP_TIMER_NUMBER; Index++) { if (TCP_TIMER_ON (Tcb->EnabledTimer, Index) && TCP_TIME_LT (Tcb->Timer[Index], mTcpTick + Tcb->NextExpire) ) { Tcb->NextExpire = TCP_SUB_TIME (Tcb->Timer[Index], mTcpTick); TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_TIMER_ON); } } } /** Enable a TCP timer. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. @param[in] Timer The index of the timer to be enabled. @param[in] TimeOut The timeout value of this timer. **/ VOID TcpSetTimer ( IN OUT TCP_CB *Tcb, IN UINT16 Timer, IN UINT32 TimeOut ) { TCP_SET_TIMER (Tcb->EnabledTimer, Timer); Tcb->Timer[Timer] = mTcpTick + TimeOut; TcpUpdateTimer (Tcb); } /** Clear one TCP timer. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. @param[in] Timer The index of the timer to be cleared. **/ VOID TcpClearTimer ( IN OUT TCP_CB *Tcb, IN UINT16 Timer ) { TCP_CLEAR_TIMER (Tcb->EnabledTimer, Timer); TcpUpdateTimer (Tcb); } /** Clear all TCP timers. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. **/ VOID TcpClearAllTimer ( IN OUT TCP_CB *Tcb ) { Tcb->EnabledTimer = 0; TcpUpdateTimer (Tcb); } /** Enable the window prober timer and set the timeout value. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. **/ VOID TcpSetProbeTimer ( IN OUT TCP_CB *Tcb ) { if (!Tcb->ProbeTimerOn) { Tcb->ProbeTime = Tcb->Rto; Tcb->ProbeTimerOn = TRUE; } else { Tcb->ProbeTime <<= 1; } if (Tcb->ProbeTime < TCP_RTO_MIN) { Tcb->ProbeTime = TCP_RTO_MIN; } else if (Tcb->ProbeTime > TCP_RTO_MAX) { Tcb->ProbeTime = TCP_RTO_MAX; } TcpSetTimer (Tcb, TCP_TIMER_PROBE, Tcb->ProbeTime); } /** Enable the keepalive timer and set the timeout value. @param[in, out] Tcb Pointer to the TCP_CB of this TCP instance. **/ VOID TcpSetKeepaliveTimer ( IN OUT TCP_CB *Tcb ) { if (TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_KEEPALIVE)) { return; } // // Set the timer to KeepAliveIdle if either // 1. the keepalive timer is off // 2. The keepalive timer is on, but the idle // is less than KeepAliveIdle, that means the // connection is alive since our last probe. // if (!TCP_TIMER_ON (Tcb->EnabledTimer, TCP_TIMER_KEEPALIVE) || (Tcb->Idle < Tcb->KeepAliveIdle) ) { TcpSetTimer (Tcb, TCP_TIMER_KEEPALIVE, Tcb->KeepAliveIdle); Tcb->KeepAliveProbes = 0; } else { TcpSetTimer (Tcb, TCP_TIMER_KEEPALIVE, Tcb->KeepAlivePeriod); } } /** Heart beat timer handler. @param[in] Context Context of the timer event, ignored. **/ VOID EFIAPI TcpTickingDpc ( IN VOID *Context ) { LIST_ENTRY *Entry; LIST_ENTRY *Next; TCP_CB *Tcb; INT16 Index; mTcpTick++; // // Don't use LIST_FOR_EACH, which isn't delete safe. // for (Entry = mTcpRunQue.ForwardLink; Entry != &mTcpRunQue; Entry = Next) { Next = Entry->ForwardLink; Tcb = NET_LIST_USER_STRUCT (Entry, TCP_CB, List); if (Tcb->State == TCP_CLOSED) { continue; } // // The connection is doing RTT measurement. // if (TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_RTT_ON)) { Tcb->RttMeasure++; } Tcb->Idle++; if (Tcb->DelayedAck != 0) { TcpSendAck (Tcb); } if ((Tcb->IpInfo->IpVersion == IP_VERSION_6) && (Tcb->Tick > 0)) { Tcb->Tick--; } // // No timer is active or no timer expired // if (!TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_TIMER_ON) || ((--Tcb->NextExpire) > 0)) { continue; } // // Call the timeout handler for each expired timer. // for (Index = 0; Index < TCP_TIMER_NUMBER; Index++) { if (TCP_TIMER_ON (Tcb->EnabledTimer, Index) && TCP_TIME_LEQ (Tcb->Timer[Index], mTcpTick)) { // // disable the timer before calling the handler // in case the handler enables it again. // TCP_CLEAR_TIMER (Tcb->EnabledTimer, Index); mTcpTimerHandler[Index](Tcb); // // The Tcb may have been deleted by the timer, or // no other timer is set. // if ((Next->BackLink != Entry) || (Tcb->EnabledTimer == 0)) { break; } } } // // If the Tcb still exist or some timer is set, update the timer // if (Index == TCP_TIMER_NUMBER) { TcpUpdateTimer (Tcb); } } } /** Heart beat timer handler, queues the DPC at TPL_CALLBACK. @param[in] Event Timer event signaled, ignored. @param[in] Context Context of the timer event, ignored. **/ VOID EFIAPI TcpTicking ( IN EFI_EVENT Event, IN VOID *Context ) { QueueDpc (TPL_CALLBACK, TcpTickingDpc, Context); }