WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 746d689

Browse files
zhhyu7xiaoxiang781216
authored andcommitted
net/tcp: add support for the CLOSE_WAIT state
CLOSE-WAIT - represents waiting for a connection termination request from the local user. TCP A TCP B 1. ESTABLISHED ESTABLISHED 2. (Close) FIN-WAIT-1 --> <SEQ=100><ACK=300><CTL=FIN,ACK> --> CLOSE-WAIT 3. FIN-WAIT-2 <-- <SEQ=300><ACK=101><CTL=ACK> <-- CLOSE-WAIT 4. (Close) TIME-WAIT <-- <SEQ=300><ACK=101><CTL=FIN,ACK> <-- LAST-ACK 5. TIME-WAIT --> <SEQ=101><ACK=301><CTL=ACK> --> CLOSED 6. (2 MSL) CLOSED in the current state, we can continue to send data until the user calls shutdown or close, then directly enter the TCP_LAST_ACK state Signed-off-by: zhanghongyu <[email protected]>
1 parent 2922761 commit 746d689

File tree

10 files changed

+395
-37
lines changed

10 files changed

+395
-37
lines changed

Documentation/components/net/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Network Support
1919
wqueuedeadlocks.rst
2020
tcp_network_perf.rst
2121
delay_act_and_tcp_perf.rst
22+
tcp_state_machine.rst
2223

2324
``net`` Directory Structure ::
2425

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
=============================
2+
NuttX TCP State Machine Notes
3+
=============================
4+
5+
This document describes how the current NuttX TCP stack implements TCP
6+
state transitions. It is based on the in-tree implementation (primarily
7+
in ``net/tcp``) and focuses on *what the code does today* rather than a
8+
generic RFC 793 description.
9+
10+
Scope
11+
=====
12+
13+
* TCP connection state is tracked per ``struct tcp_conn_s``.
14+
* State transitions happen mainly in:
15+
16+
* ``net/tcp/tcp_input.c`` (incoming segments and most transitions)
17+
* ``net/tcp/tcp_timer.c`` (timeouts and retransmissions)
18+
* ``net/tcp/tcp_conn.c`` (connect/listen-side allocation and initial state)
19+
* ``net/tcp/tcp_close.c`` (active close initiation)
20+
21+
State Representation
22+
====================
23+
24+
NuttX stores TCP state in ``tcp_conn_s::tcpstateflags``.
25+
26+
* Bits 0-3 are the state (``TCP_STATE_MASK``).
27+
* Bit 4 is a flag (``TCP_STOPPED``) used by the socket layer to stop data flow.
28+
29+
The state values are defined in ``include/nuttx/net/tcp.h``:
30+
31+
* ``TCP_CLOSED``
32+
* ``TCP_ALLOCATED`` (NuttX-internal: allocated but not yet connected)
33+
* ``TCP_SYN_RCVD``
34+
* ``TCP_SYN_SENT``
35+
* ``TCP_ESTABLISHED``
36+
* ``TCP_FIN_WAIT_1``
37+
* ``TCP_FIN_WAIT_2``
38+
* ``TCP_CLOSE_WAIT``
39+
* ``TCP_CLOSING``
40+
* ``TCP_TIME_WAIT``
41+
* ``TCP_LAST_ACK``
42+
* ``TCP_STOPPED``
43+
44+
Supported vs Unsupported (RFC State View)
45+
=========================================
46+
47+
NuttX largely follows the classic TCP state machine, the table below maps the traditional RFC 793 state names to what exists in
48+
NuttX today.
49+
50+
.. list-table:: RFC TCP states and their NuttX support
51+
:header-rows: 1
52+
:widths: auto
53+
54+
* - RFC state name
55+
- NuttX representation
56+
- Supported
57+
- Notes
58+
* - CLOSED
59+
- ``TCP_CLOSED``
60+
- Yes
61+
- Connection is unused/available.
62+
* - LISTEN
63+
- No ``tcpstateflags`` state
64+
- Partially
65+
- Listening is implemented via the listener table in ``net/tcp/tcp_listen.c``(``tcp_listenports[]``) rather than a per-connection LISTEN state.
66+
* - SYN-SENT
67+
- ``TCP_SYN_SENT``
68+
- Yes
69+
- Set by ``tcp_connect()`` in ``net/tcp/tcp_conn.c``.
70+
* - SYN-RECEIVED
71+
- ``TCP_SYN_RCVD``
72+
- Yes
73+
- Set when accepting an incoming SYN (new connection allocated for a listener).
74+
* - ESTABLISHED
75+
- ``TCP_ESTABLISHED``
76+
- Yes
77+
- Data transfer state.
78+
* - FIN-WAIT-1
79+
- ``TCP_FIN_WAIT_1``
80+
- Yes
81+
- Entered on active close (local FIN sent). However, it is currently unable to continue receiving data in this state
82+
* - FIN-WAIT-2
83+
- ``TCP_FIN_WAIT_2``
84+
- Yes
85+
- Entered after ACK for local FIN (when peer hasn't closed yet). However, it is currently unable to continue receiving data in this state
86+
* - CLOSE-WAIT
87+
- Not implemented
88+
- Yes
89+
- The TCP input path explicitly notes CLOSE_WAIT is not implemented; NuttX forces the application to close when FIN is received and moves directly toward ``TCP_LAST_ACK``.
90+
* - CLOSING
91+
- ``TCP_CLOSING``
92+
- Yes
93+
- Used for simultaneous close handling.
94+
* - LAST-ACK
95+
- ``TCP_LAST_ACK``
96+
- Yes
97+
- Used after receiving FIN and sending FIN in response.
98+
* - TIME-WAIT
99+
- ``TCP_TIME_WAIT``
100+
- Yes
101+
- Used after the close handshake; timer-driven cleanup.
102+
103+
Note on ``TCP_ALLOCATED``
104+
-------------------------
105+
106+
``TCP_ALLOCATED`` is NuttX-specific and has no direct RFC state name.
107+
It is the pre-connect/pre-accept state for a newly created socket connection.
108+
109+
High-level Transition Summary
110+
=============================
111+
112+
This section summarizes the most common state paths.
113+
114+
Active open (connect)
115+
---------------------
116+
117+
Typical client-side flow:
118+
119+
::
120+
121+
TCP_ALLOCATED
122+
-> TCP_SYN_SENT (tcp_connect() prepares SYN)
123+
-> TCP_ESTABLISHED (tcp_input receives SYN|ACK and replies ACK)
124+
125+
Passive open (listen/accept)
126+
----------------------------
127+
128+
Listening sockets are registered in the listener table (not a LISTEN state).
129+
When a SYN arrives:
130+
131+
::
132+
133+
listener in tcp_listenports[]
134+
-> new conn: TCP_SYN_RCVD (tcp_allocaccept() in tcp_conn.c)
135+
-> TCP_ESTABLISHED (tcp_input receives final ACK)
136+
-> accept() wakes up (tcp_accept_connection())
137+
138+
Graceful close (active close)
139+
-----------------------------
140+
141+
When the application initiates a close (or ``shutdown(SHUT_WR)``), the stack
142+
sends FIN and transitions:
143+
144+
::
145+
146+
TCP_ESTABLISHED
147+
-> TCP_FIN_WAIT_1
148+
-> TCP_FIN_WAIT_2 (ACK of our FIN)
149+
-> TCP_TIME_WAIT (FIN from peer)
150+
-> TCP_CLOSED (timer expiry)
151+
152+
Simultaneous close
153+
------------------
154+
155+
If FIN is received while we are in ``TCP_FIN_WAIT_1`` and our FIN has not been
156+
fully ACKed, NuttX can enter ``TCP_CLOSING``:
157+
158+
::
159+
160+
TCP_FIN_WAIT_1
161+
-> TCP_CLOSING
162+
-> TCP_TIME_WAIT (ACK of our FIN)
163+
164+
Passive close (peer closes first)
165+
---------------------------------
166+
167+
When FIN is received in ESTABLISHED, the application is notified
168+
via callbacks. the stack sends ACK and goes to ``TCP_CLOSE_WAIT``:
169+
170+
::
171+
172+
TCP_ESTABLISHED
173+
-> TCP_CLOSE_WAIT (FIN received)
174+
-> TCP_CLOSED (ACK of our FIN)
175+
176+
Detailed State Handling
177+
=======================
178+
179+
TCP_SYN_SENT
180+
------------
181+
182+
* Entered by ``tcp_connect()`` (``net/tcp/tcp_conn.c``).
183+
* On receiving ``SYN|ACK`` with a valid ACK:
184+
185+
* Parses options (e.g., MSS).
186+
* Sets ``TCP_ESTABLISHED``.
187+
* Updates ``rcvseq`` and window tracking.
188+
* Notifies the socket layer using ``TCP_CONNECTED``.
189+
190+
* On unexpected control segments or failure:
191+
192+
* The connection is aborted (``TCP_ABORT`` callback) and a RST may be sent.
193+
194+
TCP_SYN_RCVD
195+
------------
196+
197+
* Entered for a newly accepted connection when a SYN matches a listener.
198+
Allocation and initialization occur in ``tcp_allocaccept()``
199+
(``net/tcp/tcp_conn.c``).
200+
* A SYN-ACK is sent. The retransmission is handled by ``tcp_timer.c``.
201+
* On receiving the final ACK (``TCP_ACKDATA``):
202+
203+
* Transition to ``TCP_ESTABLISHED``.
204+
* ``tcp_accept_connection()`` is called to hand the connection to the
205+
listening socket/accept logic.
206+
207+
TCP_ESTABLISHED
208+
---------------
209+
210+
* Normal data transfer occurs here.
211+
* Incoming data and ACK processing is handled in ``net/tcp/tcp_input.c``.
212+
* If a FIN is received:
213+
214+
* The application is notified (``TCP_CLOSE`` flag is included in callback).
215+
* NuttX transitions to ``TCP_CLOSE_WAIT`` and sends ``ACK``.
216+
217+
TCP_CLOSE_WAIT
218+
--------------
219+
220+
* Only entered when a FIN is received in ESTABLISHED.
221+
* The application is notified (``TCP_CLOSE`` flag in callback).
222+
* NuttX can send data until the application initiates close.
223+
* On application close request:
224+
* NuttX sends FIN and transitions to ``TCP_LAST_ACK``.
225+
226+
TCP_FIN_WAIT_1
227+
--------------
228+
229+
* Entered when the application requests a graceful close.
230+
This is initiated in ``net/tcp/tcp_appsend.c`` when the callback result
231+
contains ``TCP_CLOSE``.
232+
233+
* On receiving FIN:
234+
235+
* If the FIN also ACKs our FIN and ``tx_unacked == 0``: transition to
236+
``TCP_TIME_WAIT``.
237+
* Otherwise: transition to ``TCP_CLOSING``.
238+
* In both cases, ACK the peer FIN.
239+
240+
* On receiving an ACK that completes ACK of our FIN (and no FIN from peer):
241+
242+
* Transition to ``TCP_FIN_WAIT_2``.
243+
244+
* Data received in FIN_WAIT_1:
245+
246+
* Current behavior is to send a RST and force ``TCP_CLOSED``.
247+
* The implementation notes this as a TODO to improve shutdown behavior.
248+
249+
TCP_FIN_WAIT_2
250+
--------------
251+
252+
* Waiting for the peer FIN after our FIN was ACKed.
253+
* On receiving FIN:
254+
255+
* Transition to ``TCP_TIME_WAIT``.
256+
* ACK the FIN and notify close.
257+
258+
* Data received in FIN_WAIT_2:
259+
260+
* Current behavior is to send a RST and force ``TCP_CLOSED``.
261+
262+
TCP_CLOSING
263+
-----------
264+
265+
* Simultaneous close case.
266+
* When the ACK for our FIN is received (``TCP_ACKDATA``):
267+
268+
* Transition to ``TCP_TIME_WAIT``.
269+
270+
TCP_LAST_ACK
271+
------------
272+
273+
* Entered after FIN is received in ESTABLISHED and the application chooses
274+
to close, causing the stack to send FIN.
275+
* On receiving ACK for our FIN (``TCP_ACKDATA``):
276+
277+
* Transition to ``TCP_CLOSED``.
278+
* Notify close via callback.
279+
280+
TCP_TIME_WAIT
281+
-------------
282+
283+
* NuttX responds to segments by sending an ACK.
284+
* Cleanup is timer-driven (see ``tcp_timer.c``):
285+
286+
* ``TCP_TIME_WAIT`` are handled as "wait for timeout" states.
287+
* When the per-connection timer expires, the state becomes ``TCP_CLOSED``.
288+
289+
Timers, Retransmissions, and Failure Handling
290+
=============================================
291+
292+
The TCP timer handler in ``net/tcp/tcp_timer.c`` drives:
293+
294+
* Retransmission for connections with ``tx_unacked > 0``.
295+
* State-specific retransmit behavior:
296+
297+
* ``TCP_SYN_RCVD``: retransmit SYN-ACK.
298+
* ``TCP_SYN_SENT``: retransmit SYN.
299+
* ``TCP_ESTABLISHED``: request retransmit via callback (``TCP_REXMIT``).
300+
* ``TCP_FIN_WAIT_1``, ``TCP_CLOSING``, ``TCP_LAST_ACK``: retransmit FIN|ACK.
301+
302+
* Timeout cleanup:
303+
304+
* ``TCP_SYN_RCVD``: if SYN-ACK retransmits exceed limit, the half-open
305+
connection is closed and freed.
306+
* ``TCP_SYN_SENT`` and established cases: if retransmits exceed limit, the
307+
connection is closed, the socket is notified (``TCP_TIMEDOUT``), and a
308+
RST may be sent.
309+
310+
Deviations and Notable Simplifications
311+
======================================
312+
313+
* LISTEN is not an explicit TCP state; it is represented by listener table entries.
314+
* FIN_WAIT_* data handling is currently strict: received payload data in
315+
FIN_WAIT_1/2 results in sending RST and closing the connection.
316+
* RST processing is intentionally simple (accept RST and close).
317+
318+
Where to Look in the Code
319+
=========================
320+
321+
* State definitions: ``include/nuttx/net/tcp.h``
322+
* Incoming-segment state logic: ``net/tcp/tcp_input.c``
323+
* Retransmission/timeout logic: ``net/tcp/tcp_timer.c``
324+
* Connect path / SYN_SENT setup: ``net/tcp/tcp_conn.c``
325+
* Accept path / SYN_RCVD allocation: ``net/tcp/tcp_conn.c``
326+
* Active close initiation: ``net/tcp/tcp_close.c`` and ``net/tcp/tcp_shutdown.c``
327+
* Listener table (LISTEN semantics): ``net/tcp/tcp_listen.c``

include/nuttx/net/tcp.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,10 @@
8888
# define TCP_ESTABLISHED 0x04
8989
# define TCP_FIN_WAIT_1 0x05
9090
# define TCP_FIN_WAIT_2 0x06
91-
# define TCP_CLOSING 0x07
92-
# define TCP_TIME_WAIT 0x08
93-
# define TCP_LAST_ACK 0x09
91+
# define TCP_CLOSE_WAIT 0x07
92+
# define TCP_CLOSING 0x08
93+
# define TCP_TIME_WAIT 0x09
94+
# define TCP_LAST_ACK 0x0a
9495
# define TCP_STOPPED 0x10 /* Bit 4: stopped */
9596
/* Bit 5-7: Unused, but not available */
9697

net/tcp/tcp_appsend.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,13 +194,14 @@ void tcp_appsend(FAR struct net_driver_s *dev, FAR struct tcp_conn_s *conn,
194194

195195
else if ((result & TCP_CLOSE) != 0)
196196
{
197-
conn->tcpstateflags = TCP_FIN_WAIT_1;
197+
conn->tcpstateflags = conn->tcpstateflags == TCP_CLOSE_WAIT ?
198+
TCP_LAST_ACK : TCP_FIN_WAIT_1;
198199
conn->tx_unacked = 1;
199200
conn->nrtx = 0;
200201
#ifdef CONFIG_NET_TCP_WRITE_BUFFERS
201202
conn->sndseq_max = tcp_getsequence(conn->sndseq) + 1;
202203
#endif
203-
ninfo("TCP state: TCP_FIN_WAIT_1\n");
204+
ninfo("TCP state: %d\n", conn->tcpstateflags);
204205

205206
dev->d_sndlen = 0;
206207
tcp_send(dev, conn, TCP_FIN | TCP_ACK, hdrlen);

net/tcp/tcp_close.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,8 @@ static uint16_t tcp_close_eventhandler(FAR struct net_driver_s *dev,
153153
* TCP_CLOSE is handled above.
154154
*/
155155

156-
DEBUGASSERT(conn->tcpstateflags == TCP_ESTABLISHED);
156+
DEBUGASSERT(conn->tcpstateflags == TCP_ESTABLISHED ||
157+
conn->tcpstateflags == TCP_CLOSE_WAIT);
157158

158159
/* Drop data received in this state and make sure that TCP_CLOSE
159160
* is set in the response
@@ -236,7 +237,8 @@ static inline int tcp_close_disconnect(FAR struct socket *psock)
236237
*/
237238

238239
if ((conn->tcpstateflags == TCP_ESTABLISHED ||
239-
conn->tcpstateflags == TCP_LAST_ACK) &&
240+
conn->tcpstateflags == TCP_LAST_ACK ||
241+
conn->tcpstateflags == TCP_CLOSE_WAIT) &&
240242
(conn->clscb = tcp_callback_alloc(conn)) != NULL)
241243
{
242244
/* Free rx buffers of the connection immediately */

net/tcp/tcp_devpoll.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ void tcp_poll(FAR struct net_driver_s *dev, FAR struct tcp_conn_s *conn)
109109

110110
/* Verify that the connection is established. */
111111

112-
if ((conn->tcpstateflags & TCP_STATE_MASK) == TCP_ESTABLISHED)
112+
if ((conn->tcpstateflags & TCP_STATE_MASK) == TCP_ESTABLISHED ||
113+
(conn->tcpstateflags & TCP_STATE_MASK) == TCP_CLOSE_WAIT)
113114
{
114115
/* Set up for the callback. We can't know in advance if the
115116
* application is going to send a IPv4 or an IPv6 packet, so this

0 commit comments

Comments
 (0)