1 /***************************************************************************
2 * Copyright (c) 2024 Microsoft Corporation
3 *
4 * This program and the accompanying materials are made available under the
5 * terms of the MIT License which is available at
6 * https://opensource.org/licenses/MIT.
7 *
8 * SPDX-License-Identifier: MIT
9 **************************************************************************/
10
11
12 /**************************************************************************/
13 /**************************************************************************/
14 /** */
15 /** NetX Component */
16 /** */
17 /** Transmission Control Protocol (TCP) */
18 /** */
19 /**************************************************************************/
20 /**************************************************************************/
21
22 #define NX_SOURCE_CODE
23
24
25 /* Include necessary system files. */
26
27 #include "nx_api.h"
28 #include "nx_ip.h"
29 #include "nx_tcp.h"
30 #include "tx_thread.h"
31 #include "nx_ipv6.h"
32
33 /**************************************************************************/
34 /* */
35 /* FUNCTION RELEASE */
36 /* */
37 /* _nx_tcp_socket_disconnect PORTABLE C */
38 /* 6.1.8 */
39 /* AUTHOR */
40 /* */
41 /* Yuxin Zhou, Microsoft Corporation */
42 /* */
43 /* DESCRIPTION */
44 /* */
45 /* This function handles the disconnect request for both active and */
46 /* passive calls. */
47 /* */
48 /* INPUT */
49 /* */
50 /* socket_ptr Pointer to TCP client socket */
51 /* wait_option Suspension option */
52 /* */
53 /* OUTPUT */
54 /* */
55 /* status Completion status */
56 /* */
57 /* CALLS */
58 /* */
59 /* _nx_tcp_connect_cleanup Clear connect suspension */
60 /* _nx_tcp_disconnect_cleanup Clear disconnect suspension */
61 /* _nx_tcp_packet_send_fin Send FIN message */
62 /* _nx_tcp_packet_send_rst Send RST on no timeout */
63 /* _nx_tcp_receive_cleanup Clear receive suspension */
64 /* _nx_tcp_transmit_cleanup Clear transmit suspension */
65 /* _nx_tcp_socket_thread_suspend Suspend calling thread */
66 /* _nx_tcp_socket_transmit_queue_flush Release all transmit packets */
67 /* _nx_tcp_socket_block_cleanup Cleanup the socket block */
68 /* tx_mutex_get Get protection */
69 /* tx_mutex_put Release protection */
70 /* */
71 /* CALLED BY */
72 /* */
73 /* Application Code */
74 /* */
75 /* RELEASE HISTORY */
76 /* */
77 /* DATE NAME DESCRIPTION */
78 /* */
79 /* 05-19-2020 Yuxin Zhou Initial Version 6.0 */
80 /* 09-30-2020 Yuxin Zhou Modified comment(s), */
81 /* resulting in version 6.1 */
82 /* 08-02-2021 Yuxin Zhou Modified comment(s), and */
83 /* supported TCP/IP offload, */
84 /* resulting in version 6.1.8 */
85 /* */
86 /**************************************************************************/
_nx_tcp_socket_disconnect(NX_TCP_SOCKET * socket_ptr,ULONG wait_option)87 UINT _nx_tcp_socket_disconnect(NX_TCP_SOCKET *socket_ptr, ULONG wait_option)
88 {
89
90 #ifndef NX_DISABLE_RESET_DISCONNECT
91 NX_TCP_HEADER tcp_header;
92 #endif
93 #ifdef NX_ENABLE_TCPIP_OFFLOAD
94 NX_INTERFACE *interface_ptr;
95 #endif /* NX_ENABLE_TCPIP_OFFLOAD */
96 UINT status;
97 NX_IP *ip_ptr;
98
99 /* Setup IP pointer. */
100 ip_ptr = socket_ptr -> nx_tcp_socket_ip_ptr;
101
102 /* If trace is enabled, insert this event into the trace buffer. */
103 NX_TRACE_IN_LINE_INSERT(NX_TRACE_TCP_SOCKET_DISCONNECT, ip_ptr, socket_ptr, wait_option, socket_ptr -> nx_tcp_socket_state, NX_TRACE_TCP_EVENTS, 0, 0);
104
105 /* Default status to success. */
106 status = NX_SUCCESS;
107
108 /* Obtain the IP mutex so we can access socket and IP information. */
109 tx_mutex_get(&(ip_ptr -> nx_ip_protection), TX_WAIT_FOREVER);
110
111 #ifndef NX_DISABLE_TCP_INFO
112 /* Increment the TCP disconnections count. */
113 ip_ptr -> nx_ip_tcp_disconnections++;
114 #endif
115
116 /* Determine if the socket is in a state not valid for a disconnect. */
117 if ((socket_ptr -> nx_tcp_socket_state != NX_TCP_ESTABLISHED) &&
118 (socket_ptr -> nx_tcp_socket_state != NX_TCP_SYN_SENT) &&
119 (socket_ptr -> nx_tcp_socket_state != NX_TCP_SYN_RECEIVED) &&
120 (socket_ptr -> nx_tcp_socket_state != NX_TCP_CLOSE_WAIT))
121 {
122
123 /* Release protection. */
124 tx_mutex_put(&(ip_ptr -> nx_ip_protection));
125
126 /* Return a not connected error code. */
127 return(NX_NOT_CONNECTED);
128 }
129
130 #ifdef NX_ENABLE_TCP_KEEPALIVE
131 /* Clear the TCP Keepalive timer to disable it for this socket (only needed when
132 the socket is connected. */
133 socket_ptr -> nx_tcp_socket_keepalive_timeout = 0;
134 socket_ptr -> nx_tcp_socket_keepalive_retries = 0;
135 #endif
136
137 #ifdef NX_ENABLE_TCPIP_OFFLOAD
138 interface_ptr = socket_ptr -> nx_tcp_socket_connect_interface;
139 if (interface_ptr &&
140 (interface_ptr -> nx_interface_capability_flag & NX_INTERFACE_CAPABILITY_TCPIP_OFFLOAD) &&
141 (interface_ptr -> nx_interface_tcpip_offload_handler))
142 {
143
144 /* Let TCP/IP offload interface close the connection. */
145 interface_ptr -> nx_interface_tcpip_offload_handler(ip_ptr, interface_ptr, socket_ptr,
146 NX_TCPIP_OFFLOAD_TCP_SOCKET_DISCONNECT,
147 NX_NULL, NX_NULL, NX_NULL, 0, NX_NULL,
148 wait_option);
149
150 /* Reset socket state. */
151 socket_ptr -> nx_tcp_socket_state = NX_TCP_CLOSED;
152 }
153 else
154 #endif /* NX_ENABLE_TCPIP_OFFLOAD */
155
156 /* Determine if the connection wasn't fully completed. */
157 if ((socket_ptr -> nx_tcp_socket_state == NX_TCP_SYN_SENT) ||
158 (socket_ptr -> nx_tcp_socket_state == NX_TCP_SYN_RECEIVED))
159 {
160
161 /* Connection wasn't fully completed, reset to the proper socket state. */
162 if (socket_ptr -> nx_tcp_socket_client_type)
163 {
164
165 /* If trace is enabled, insert this event into the trace buffer. */
166 NX_TRACE_IN_LINE_INSERT(NX_TRACE_INTERNAL_TCP_STATE_CHANGE, ip_ptr, socket_ptr, socket_ptr -> nx_tcp_socket_state, NX_TCP_CLOSED, NX_TRACE_INTERNAL_EVENTS, 0, 0);
167
168 if (socket_ptr -> nx_tcp_socket_state == NX_TCP_SYN_RECEIVED)
169 {
170
171 /* Setup FIN timeout. */
172 socket_ptr -> nx_tcp_socket_timeout = socket_ptr -> nx_tcp_socket_timeout_rate;
173 socket_ptr -> nx_tcp_socket_timeout_retries = 0;
174
175 /* Increment the sequence number. */
176 socket_ptr -> nx_tcp_socket_tx_sequence++;
177
178 /* Send FIN packet. */
179 _nx_tcp_packet_send_fin(socket_ptr, (socket_ptr -> nx_tcp_socket_tx_sequence - 1));
180 }
181
182 /* Client socket, return to a CLOSED state. */
183 socket_ptr -> nx_tcp_socket_state = NX_TCP_CLOSED;
184 }
185 else
186 {
187
188 /* If trace is enabled, insert this event into the trace buffer. */
189 NX_TRACE_IN_LINE_INSERT(NX_TRACE_INTERNAL_TCP_STATE_CHANGE, ip_ptr, socket_ptr, socket_ptr -> nx_tcp_socket_state, NX_TCP_LISTEN_STATE, NX_TRACE_INTERNAL_EVENTS, 0, 0);
190
191 if ((socket_ptr -> nx_tcp_socket_state == NX_TCP_SYN_RECEIVED) &&
192 (socket_ptr -> nx_tcp_socket_connect_interface != NX_NULL))
193 {
194
195 /* Setup FIN timeout. */
196 socket_ptr -> nx_tcp_socket_timeout = socket_ptr -> nx_tcp_socket_timeout_rate;
197 socket_ptr -> nx_tcp_socket_timeout_retries = 0;
198
199 /* Increment the sequence number. */
200 socket_ptr -> nx_tcp_socket_tx_sequence++;
201
202 /* Send FIN packet. */
203 _nx_tcp_packet_send_fin(socket_ptr, (socket_ptr -> nx_tcp_socket_tx_sequence - 1));
204 }
205
206 /* Server socket, return to LISTEN state. */
207 socket_ptr -> nx_tcp_socket_state = NX_TCP_LISTEN_STATE;
208
209 /* Move back the acknowledgment number just in case there is a retry. */
210 socket_ptr -> nx_tcp_socket_rx_sequence--;
211 }
212
213 /* Socket is no longer active. Clear the timeout. */
214 socket_ptr -> nx_tcp_socket_timeout = 0;
215 }
216
217 #ifndef NX_DISABLE_RESET_DISCONNECT
218
219 /* Determine if there is no timeout associated with the disconnect. If this is the case,
220 we will send a RST and simply enter a closed state. */
221 else if (wait_option == NX_NO_WAIT)
222 {
223
224 /* No timeout was specified, simply send a RST and enter a closed or listen state. */
225
226 /* Clear this field so the RST packet handler knows this is a fake header. */
227 tcp_header.nx_tcp_header_word_3 = NX_TCP_ACK_BIT;
228
229 /* Send the RST packet. We just want to create a fake header, so assume this packet is incoming packet. */
230 tcp_header.nx_tcp_acknowledgment_number = socket_ptr -> nx_tcp_socket_tx_sequence;
231 tcp_header.nx_tcp_sequence_number = socket_ptr -> nx_tcp_socket_rx_sequence;
232 _nx_tcp_packet_send_rst(socket_ptr, &tcp_header);
233
234 /* Cleanup the transmission control block. */
235 _nx_tcp_socket_block_cleanup(socket_ptr);
236
237 /* No suspension is requested, just set the return status to in progress. */
238 status = NX_IN_PROGRESS;
239 }
240 #endif
241
242 /* Determine if this is an active disconnect, i.e. initiated by the application rather
243 than in response to a disconnect from the connected socket. */
244 else if (socket_ptr -> nx_tcp_socket_state != NX_TCP_CLOSE_WAIT)
245 {
246
247 /* Yes, this disconnect was initiated by the application. Initiate the disconnect
248 process. */
249
250 /* If trace is enabled, insert this event into the trace buffer. */
251 NX_TRACE_IN_LINE_INSERT(NX_TRACE_INTERNAL_TCP_STATE_CHANGE, ip_ptr, socket_ptr, socket_ptr -> nx_tcp_socket_state, NX_TCP_FIN_WAIT_1, NX_TRACE_INTERNAL_EVENTS, 0, 0);
252
253 /* Move the TCP state to FIN WAIT 1 state, the first state of an active close. */
254 socket_ptr -> nx_tcp_socket_state = NX_TCP_FIN_WAIT_1;
255
256 /* Determine if the transmit queue is empty. Only setup a FIN timeout here when
257 there are no more transmit packets waiting to be ACKed. If there are transmit
258 packets still waiting, the FIN timeout will be setup when the transmit queue is completely
259 acknowledged. */
260 if (socket_ptr -> nx_tcp_socket_transmit_sent_head == NX_NULL)
261 {
262
263 /* No transmit packets queue, setup FIN timeout. */
264 socket_ptr -> nx_tcp_socket_timeout = socket_ptr -> nx_tcp_socket_timeout_rate;
265 socket_ptr -> nx_tcp_socket_timeout_retries = 0;
266 }
267
268 /* Increment the sequence number. */
269 socket_ptr -> nx_tcp_socket_tx_sequence++;
270
271 /* Send FIN packet. */
272 _nx_tcp_packet_send_fin(socket_ptr, (socket_ptr -> nx_tcp_socket_tx_sequence - 1));
273 }
274 else
275 {
276
277 /* This disconnect request is coming after the other side of the TCP connection
278 initiated a disconnect. */
279
280 /* If trace is enabled, insert this event into the trace buffer. */
281 NX_TRACE_IN_LINE_INSERT(NX_TRACE_INTERNAL_TCP_STATE_CHANGE, ip_ptr, socket_ptr, socket_ptr -> nx_tcp_socket_state, NX_TCP_LAST_ACK, NX_TRACE_INTERNAL_EVENTS, 0, 0);
282
283 /* Move the TCP state to wait for the last ACK message for the complete disconnect. */
284 socket_ptr -> nx_tcp_socket_state = NX_TCP_LAST_ACK;
285
286 /* Send a FIN message back to the side of the connection that initiated the
287 disconnect. */
288
289 /* Determine if the transmit queue is empty. Only setup a FIN timeout here when
290 there are no more transmit packets waiting to be ACKed. If there are transmit
291 packets still waiting, the FIN timeout will be setup when the transmit queue is completely
292 acknowledged. */
293 if (socket_ptr -> nx_tcp_socket_transmit_sent_head == NX_NULL)
294 {
295
296 /* No transmit packets queue, setup FIN timeout. */
297 socket_ptr -> nx_tcp_socket_timeout = socket_ptr -> nx_tcp_socket_timeout_rate;
298 socket_ptr -> nx_tcp_socket_timeout_retries = 0;
299 }
300
301 /* Increment the sequence number. */
302 socket_ptr -> nx_tcp_socket_tx_sequence++;
303
304 /* Send FIN packet. */
305 _nx_tcp_packet_send_fin(socket_ptr, (socket_ptr -> nx_tcp_socket_tx_sequence - 1));
306 }
307
308 /* Optionally suspend the thread. If timeout occurs, return a disconnect timeout status. If
309 immediate response is selected, return a disconnect in progress status. Only on a real
310 disconnect should success be returned. */
311 if ((socket_ptr -> nx_tcp_socket_state != NX_TCP_CLOSED) && (socket_ptr -> nx_tcp_socket_state != NX_TCP_LISTEN_STATE) &&
312 #ifdef NX_DISABLE_RESET_DISCONNECT
313 (wait_option) &&
314 #endif
315 (_tx_thread_current_ptr != &(ip_ptr -> nx_ip_thread)))
316 {
317
318 /* Suspend the thread on socket disconnect. */
319 _nx_tcp_socket_thread_suspend(&(socket_ptr -> nx_tcp_socket_disconnect_suspended_thread), _nx_tcp_disconnect_cleanup, socket_ptr, &(ip_ptr -> nx_ip_protection), wait_option);
320
321 /* Reobtain the IP mutex. */
322 tx_mutex_get(&(ip_ptr -> nx_ip_protection), TX_WAIT_FOREVER);
323
324 /* Determine if the socket is in the timed wait state. */
325 if (socket_ptr -> nx_tcp_socket_state != NX_TCP_TIMED_WAIT)
326 {
327
328 /* Cleanup the transmission control block. */
329 _nx_tcp_socket_block_cleanup(socket_ptr);
330 }
331
332 /* Use the thread return the completion code. */
333 status = _tx_thread_current_ptr -> tx_thread_suspend_status;
334 }
335
336 /* We now need to check for any remaining sent packets in the transmit queue.
337 If found they need to be released. */
338 if (socket_ptr -> nx_tcp_socket_transmit_sent_head)
339 {
340
341 /* Release all transmit packets. */
342 _nx_tcp_socket_transmit_queue_flush(socket_ptr);
343 }
344
345 /* Clear any connection suspension on this socket. */
346 if (socket_ptr -> nx_tcp_socket_connect_suspended_thread)
347 {
348
349 /* Call the connect thread suspension cleanup routine. */
350 _nx_tcp_connect_cleanup(socket_ptr -> nx_tcp_socket_connect_suspended_thread NX_CLEANUP_ARGUMENT);
351 }
352
353 /* Clear all receive thread suspensions on this socket. */
354 while (socket_ptr -> nx_tcp_socket_receive_suspension_list)
355 {
356
357 /* Call the receive thread suspension cleanup routine. */
358 _nx_tcp_receive_cleanup(socket_ptr -> nx_tcp_socket_receive_suspension_list NX_CLEANUP_ARGUMENT);
359 }
360
361 /* Clear all transmit thread suspensions on this socket. */
362 while (socket_ptr -> nx_tcp_socket_transmit_suspension_list)
363 {
364
365 /* Call the receive thread suspension cleanup routine. */
366 _nx_tcp_transmit_cleanup(socket_ptr -> nx_tcp_socket_transmit_suspension_list NX_CLEANUP_ARGUMENT);
367 }
368
369 /* Release the IP protection. */
370 tx_mutex_put(&(ip_ptr -> nx_ip_protection));
371
372 /* Return in-progress completion status. */
373 return(status);
374 }
375
376