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