|
| 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`` |
0 commit comments