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_packet.h"
29 #include "nx_ip.h"
30 #include "nx_tcp.h"
31 #ifdef FEATURE_NX_IPV6
32 #include "nx_ipv6.h"
33 #endif /* FEATURE_NX_IPV6 */
34 #ifdef NX_IPSEC_ENABLE
35 #include "nx_ipsec.h"
36 #endif /* NX_IPSEC_ENABLE */
37 
38 /**************************************************************************/
39 /*                                                                        */
40 /*  FUNCTION                                               RELEASE        */
41 /*                                                                        */
42 /*    _nx_tcp_socket_retransmit                           PORTABLE C      */
43 /*                                                           6.4.0        */
44 /*  AUTHOR                                                                */
45 /*                                                                        */
46 /*    Yuxin Zhou, Microsoft Corporation                                   */
47 /*                                                                        */
48 /*  DESCRIPTION                                                           */
49 /*                                                                        */
50 /*    This function retransmit a TCP packet.                              */
51 /*                                                                        */
52 /*  INPUT                                                                 */
53 /*                                                                        */
54 /*    ip_ptr                                IP instance pointer           */
55 /*    socket_ptr                            Pointer to owning socket      */
56 /*    need_fast_retransmit                  Need fast retransmit or not   */
57 /*                                                                        */
58 /*  OUTPUT                                                                */
59 /*                                                                        */
60 /*    None                                                                */
61 /*                                                                        */
62 /*  CALLS                                                                 */
63 /*                                                                        */
64 /*    _nx_tcp_packet_send_probe             Send zero window probe        */
65 /*    _nx_ip_checksum_compute               Calculate TCP checksum        */
66 /*    _nx_ip_packet_send                    Resend the transmit packet    */
67 /*    _nx_ipv6_packet_send                  Resend the transmit packet    */
68 /*                                                                        */
69 /*  CALLED BY                                                             */
70 /*                                                                        */
71 /*    _nx_tcp_fast_periodic_processing      Process TCP packet for socket */
72 /*    _nx_tcp_socket_state_ack_check        Process ACK number            */
73 /*                                                                        */
74 /*  RELEASE HISTORY                                                       */
75 /*                                                                        */
76 /*    DATE              NAME                      DESCRIPTION             */
77 /*                                                                        */
78 /*  05-19-2020     Yuxin Zhou               Initial Version 6.0           */
79 /*  09-30-2020     Yuxin Zhou               Modified comment(s),          */
80 /*                                            resulting in version 6.1    */
81 /*  12-31-2023     Yajun Xia                Modified comment(s),          */
82 /*                                            supported VLAN,             */
83 /*                                            resulting in version 6.4.0  */
84 /*                                                                        */
85 /**************************************************************************/
_nx_tcp_socket_retransmit(NX_IP * ip_ptr,NX_TCP_SOCKET * socket_ptr,UINT need_fast_retransmit)86 VOID  _nx_tcp_socket_retransmit(NX_IP *ip_ptr, NX_TCP_SOCKET *socket_ptr, UINT need_fast_retransmit)
87 {
88 NX_PACKET *packet_ptr;
89 ULONG      window;
90 ULONG      original_acknowledgment_number;
91 ULONG      original_header_word_3;
92 ULONG      original_header_word_4;
93 ULONG      available;
94 ULONG      window_size;
95 
96     /* If the receiver winodw is zero, we enter the zero window probe phase
97        RFC 793 Sec 3.7, p42: keep send new data.
98 
99        In the zero window probe phase, we send the zero window probe, and increase
100        exponentially the interval between successive probes.
101        RFC 1122 Sec 4.2.2.17, p92.  */
102     if (socket_ptr -> nx_tcp_socket_tx_window_advertised == 0)
103     {
104 
105         /* Pickup the head of the transmit queue.  */
106         packet_ptr =  socket_ptr -> nx_tcp_socket_transmit_sent_head;
107 
108         if (packet_ptr)
109         {
110 
111         /* Get one byte from send queue. */
112         /* Pick up the pointer to the head of the TCP packet.  */
113         /*lint -e{927} -e{826} suppress cast of pointer to pointer, since it is necessary  */
114         NX_TCP_HEADER *header_ptr =  (NX_TCP_HEADER *)packet_ptr -> nx_packet_prepend_ptr;
115 
116             NX_CHANGE_ULONG_ENDIAN(header_ptr -> nx_tcp_header_word_3);
117             NX_CHANGE_ULONG_ENDIAN(header_ptr -> nx_tcp_sequence_number);
118 
119             /* Get sequence number and first byte. */
120             socket_ptr -> nx_tcp_socket_zero_window_probe_data = *(packet_ptr -> nx_packet_prepend_ptr + ((header_ptr -> nx_tcp_header_word_3 >> 28) << 2));
121 
122             /* Now set zero window probe started. */
123             socket_ptr -> nx_tcp_socket_zero_window_probe_has_data = NX_TRUE;
124             socket_ptr -> nx_tcp_socket_zero_window_probe_sequence = header_ptr -> nx_tcp_sequence_number;
125             socket_ptr -> nx_tcp_socket_zero_window_probe_failure = 0;
126 
127             NX_CHANGE_ULONG_ENDIAN(header_ptr -> nx_tcp_sequence_number);
128             NX_CHANGE_ULONG_ENDIAN(header_ptr -> nx_tcp_header_word_3);
129         }
130         else if (socket_ptr -> nx_tcp_socket_zero_window_probe_has_data == NX_FALSE)
131         {
132             return;
133         }
134 
135         /* In the zero window probe phase, we send the zero window probe, and increase
136            exponentially the interval between successive probes.  */
137 
138         /* Increment the retry counter.  */
139         socket_ptr -> nx_tcp_socket_timeout_retries++;
140         socket_ptr -> nx_tcp_socket_zero_window_probe_failure++;
141 
142         /* Setup the next timeout.  */
143         socket_ptr -> nx_tcp_socket_timeout = socket_ptr -> nx_tcp_socket_timeout_rate <<
144             (socket_ptr -> nx_tcp_socket_timeout_retries * socket_ptr -> nx_tcp_socket_timeout_shift);
145 
146         /* Send the zero window probe.  */
147         _nx_tcp_packet_send_probe(socket_ptr, socket_ptr -> nx_tcp_socket_zero_window_probe_sequence,
148                                   socket_ptr -> nx_tcp_socket_zero_window_probe_data);
149 
150         return;
151     }
152     else if (socket_ptr -> nx_tcp_socket_zero_window_probe_has_data == NX_TRUE)
153     {
154 
155         /* If advertised window isn't zero, reset zero window probe flag. */
156         socket_ptr -> nx_tcp_socket_zero_window_probe_has_data = NX_FALSE;
157     }
158 
159     /* Increment the retry counter only if the receiver window is open. */
160     /* Increment the retry counter.  */
161     socket_ptr -> nx_tcp_socket_timeout_retries++;
162 
163     if ((need_fast_retransmit == NX_TRUE) || (socket_ptr -> nx_tcp_socket_fast_recovery == NX_FALSE))
164     {
165 
166         /* Timed out on an outgoing packet.  Enter slow start mode. */
167         /* Compute the flight size / 2 value. */
168         window = socket_ptr -> nx_tcp_socket_tx_outstanding_bytes >> 1;
169 
170         /* Make sure we have at least 2 * MSS */
171         if (window < (socket_ptr -> nx_tcp_socket_connect_mss << 1))
172         {
173             window = socket_ptr -> nx_tcp_socket_connect_mss << 1;
174         }
175 
176         /* Set the slow_start_threshold */
177         socket_ptr -> nx_tcp_socket_tx_slow_start_threshold = window;
178 
179         /* Set the current window to be MSS size. */
180         socket_ptr -> nx_tcp_socket_tx_window_congestion = socket_ptr -> nx_tcp_socket_connect_mss;
181 
182         /* Determine if this socket needs fast retransmit.  */
183         if (need_fast_retransmit == NX_TRUE)
184         {
185 
186             /* Update cwnd to ssthreshold plus 3 * MSS.  */
187             socket_ptr -> nx_tcp_socket_tx_window_congestion += window + (socket_ptr -> nx_tcp_socket_connect_mss << 1);
188 
189             /* Now TCP is in fast recovery procedure. */
190             socket_ptr -> nx_tcp_socket_fast_recovery = NX_TRUE;
191 
192             /* Update the transmit sequence that enters fast transmit. */
193             socket_ptr -> nx_tcp_socket_tx_sequence_recover = socket_ptr -> nx_tcp_socket_tx_sequence - 1;
194         }
195     }
196 
197     /* Setup the next timeout.  */
198     socket_ptr -> nx_tcp_socket_timeout = socket_ptr -> nx_tcp_socket_timeout_rate <<
199         (socket_ptr -> nx_tcp_socket_timeout_retries * socket_ptr -> nx_tcp_socket_timeout_shift);
200 
201     /* Get available size of packet that can be sent. */
202     available = socket_ptr -> nx_tcp_socket_tx_window_congestion;
203 
204     /* Pickup the head of the transmit queue.  */
205     packet_ptr =  socket_ptr -> nx_tcp_socket_transmit_sent_head;
206 
207     /* Determine if the packet has been released by the
208        application I/O driver.  */
209     /*lint -e{923} suppress cast of ULONG to pointer.  */
210     while (packet_ptr && (packet_ptr -> nx_packet_queue_next == (NX_PACKET *)NX_DRIVER_TX_DONE))
211     {
212 
213     /* Update the ACK number in case it has changed since the data was originally transmitted. */
214     ULONG          checksum;
215     NX_TCP_HEADER *header_ptr;
216     ULONG         *source_ip = NX_NULL, *dest_ip = NX_NULL;
217     NX_PACKET     *next_ptr;
218 #if defined(NX_DISABLE_TCP_TX_CHECKSUM) || defined(NX_ENABLE_INTERFACE_CAPABILITY) || defined(NX_IPSEC_ENABLE)
219     UINT           compute_checksum = 1;
220 #endif /* defined(NX_DISABLE_TCP_TX_CHECKSUM) || defined(NX_ENABLE_INTERFACE_CAPABILITY) || defined(NX_IPSEC_ENABLE) */
221 
222 #ifdef NX_DISABLE_TCP_TX_CHECKSUM
223         compute_checksum = 0;
224 #endif /* NX_DISABLE_TCP_TX_CHECKSUM */
225 
226         if (packet_ptr -> nx_packet_length > (available + sizeof(NX_TCP_HEADER)))
227         {
228 
229             /* This packet can not be sent. */
230             break;
231         }
232 
233         /* Decrease the available size. */
234         available -= (packet_ptr -> nx_packet_length - (ULONG)sizeof(NX_TCP_HEADER));
235 
236         /* Pickup next packet. */
237         next_ptr = packet_ptr -> nx_packet_union_next.nx_packet_tcp_queue_next;
238 
239 #ifndef NX_DISABLE_IPV4
240         /* Is this an IPv4 connection? */
241         if (socket_ptr -> nx_tcp_socket_connect_ip.nxd_ip_version == NX_IP_VERSION_V4)
242         {
243 
244             packet_ptr -> nx_packet_ip_version = NX_IP_VERSION_V4;
245 
246             /* Get the source and destination addresses. */
247             source_ip = &socket_ptr -> nx_tcp_socket_connect_interface -> nx_interface_ip_address;
248             dest_ip = &socket_ptr -> nx_tcp_socket_connect_ip.nxd_ip_address.v4;
249         }
250 #endif /* !NX_DISABLE_IPV4  */
251 
252 #ifdef FEATURE_NX_IPV6
253         if (socket_ptr -> nx_tcp_socket_connect_ip.nxd_ip_version == NX_IP_VERSION_V6)
254         {
255 
256             /* Set the packet for IPv6 connectivity. */
257             packet_ptr -> nx_packet_ip_version = NX_IP_VERSION_V6;
258 
259             /* Get the source and destination addresses. */
260             source_ip = socket_ptr -> nx_tcp_socket_ipv6_addr -> nxd_ipv6_address;
261             dest_ip = socket_ptr -> nx_tcp_socket_connect_ip.nxd_ip_address.v6;
262         }
263 #endif /* FEATURE_NX_IPV6 */
264 
265         /* Pick up the pointer to the head of the TCP packet.  */
266         /*lint -e{927} -e{826} suppress cast of pointer to pointer, since it is necessary  */
267         header_ptr =  (NX_TCP_HEADER *)packet_ptr -> nx_packet_prepend_ptr;
268 
269         /* Record the original data.  */
270         original_acknowledgment_number = header_ptr -> nx_tcp_acknowledgment_number;
271         original_header_word_3 = header_ptr -> nx_tcp_header_word_3;
272         original_header_word_4 = header_ptr -> nx_tcp_header_word_4;
273 
274         /* Update the ACK number in the TCP header.  */
275         header_ptr -> nx_tcp_acknowledgment_number = socket_ptr -> nx_tcp_socket_rx_sequence;
276 
277         /* Convert to network byte order for checksum */
278         NX_CHANGE_ULONG_ENDIAN(header_ptr -> nx_tcp_acknowledgment_number);
279 
280         /* Set window size. */
281 #ifdef NX_ENABLE_TCP_WINDOW_SCALING
282         window_size = socket_ptr -> nx_tcp_socket_rx_window_current >> socket_ptr -> nx_tcp_rcv_win_scale_value;
283 
284         /* Make sure the window_size is less than 0xFFFF. */
285         if (window_size > 0xFFFF)
286         {
287             window_size = 0xFFFF;
288         }
289 #else
290         window_size = socket_ptr -> nx_tcp_socket_rx_window_current;
291 #endif /* NX_ENABLE_TCP_WINDOW_SCALING */
292 
293         header_ptr -> nx_tcp_header_word_3 =        NX_TCP_HEADER_SIZE | NX_TCP_ACK_BIT | NX_TCP_PSH_BIT | window_size;
294 
295         /* Swap the content to network byte order. */
296         NX_CHANGE_ULONG_ENDIAN(header_ptr -> nx_tcp_header_word_3);
297 
298         /* Convert back to host byte order to so we can zero out the checksum. */
299         NX_CHANGE_ULONG_ENDIAN(header_ptr -> nx_tcp_header_word_4);
300 
301         /* Remember the last ACKed sequence and the last reported window size.  */
302         socket_ptr -> nx_tcp_socket_rx_sequence_acked =    socket_ptr -> nx_tcp_socket_rx_sequence;
303         socket_ptr -> nx_tcp_socket_rx_window_last_sent =  socket_ptr -> nx_tcp_socket_rx_window_current;
304 
305         /* Zero out existing checksum before computing new one. */
306         header_ptr -> nx_tcp_header_word_4 = header_ptr -> nx_tcp_header_word_4 & 0x0000FFFF;
307 
308         /* Convert back to network byte order to so we can do the checksum. */
309         NX_CHANGE_ULONG_ENDIAN(header_ptr -> nx_tcp_header_word_4);
310 
311 
312 #ifdef NX_ENABLE_INTERFACE_CAPABILITY
313         if (socket_ptr -> nx_tcp_socket_connect_interface -> nx_interface_capability_flag & NX_INTERFACE_CAPABILITY_TCP_TX_CHECKSUM)
314         {
315             compute_checksum = 0;
316         }
317 #endif /* NX_ENABLE_INTERFACE_CAPABILITY */
318 
319 #ifdef NX_IPSEC_ENABLE
320         if ((packet_ptr -> nx_packet_ipsec_sa_ptr != NX_NULL) &&
321             (((NX_IPSEC_SA *)(packet_ptr -> nx_packet_ipsec_sa_ptr)) -> nx_ipsec_sa_encryption_method != NX_CRYPTO_NONE))
322         {
323             compute_checksum = 1;
324         }
325 #endif /* NX_IPSEC_ENABLE */
326 
327 #if defined(NX_DISABLE_TCP_TX_CHECKSUM) || defined(NX_ENABLE_INTERFACE_CAPABILITY) || defined(NX_IPSEC_ENABLE)
328         if (compute_checksum)
329 #endif /* defined(NX_DISABLE_TCP_TX_CHECKSUM) || defined(NX_ENABLE_INTERFACE_CAPABILITY) || defined(NX_IPSEC_ENABLE) */
330         {
331             /* Calculate the TCP checksum without protection.  */
332             checksum =  _nx_ip_checksum_compute(packet_ptr, NX_PROTOCOL_TCP,
333                                                 packet_ptr -> nx_packet_length,
334                                                 source_ip, dest_ip);
335             checksum = ~checksum & NX_LOWER_16_MASK;
336 
337             /* Convert back to host byte order */
338             NX_CHANGE_ULONG_ENDIAN(header_ptr -> nx_tcp_header_word_4);
339 
340             /* Move the checksum into header.  */
341             header_ptr -> nx_tcp_header_word_4 =  header_ptr -> nx_tcp_header_word_4 | (checksum << NX_SHIFT_BY_16);
342 
343             /* Convert back to network byte order for transmit. */
344             NX_CHANGE_ULONG_ENDIAN(header_ptr -> nx_tcp_header_word_4);
345         }
346 #ifdef NX_ENABLE_INTERFACE_CAPABILITY
347         else
348         {
349             packet_ptr -> nx_packet_interface_capability_flag |= NX_INTERFACE_CAPABILITY_TCP_TX_CHECKSUM;
350         }
351 #endif /* NX_ENABLE_INTERFACE_CAPABILITY */
352 
353         /* Determine if the retransmitted packet is identical to the original packet.
354            RFC1122, Section3.2.1.5, Page32-33. RFC1122, Section4.2.2.15, Page90-91.  */
355         if ((header_ptr -> nx_tcp_acknowledgment_number == original_acknowledgment_number) &&
356             (header_ptr -> nx_tcp_header_word_3 == original_header_word_3) &&
357             (header_ptr -> nx_tcp_header_word_4 == original_header_word_4))
358         {
359 
360             /* Yes, identical packet, update the identification flag.  */
361             packet_ptr -> nx_packet_identical_copy = NX_TRUE;
362         }
363 
364 
365 #ifndef NX_DISABLE_TCP_INFO
366         /* Increment the TCP retransmit count.  */
367         ip_ptr -> nx_ip_tcp_retransmit_packets++;
368 
369         /* Increment the TCP retransmit count for the socket.  */
370         socket_ptr -> nx_tcp_socket_retransmit_packets++;
371 #endif
372 
373 #ifdef NX_ENABLE_VLAN
374         if (socket_ptr -> nx_tcp_socket_vlan_priority != NX_VLAN_PRIORITY_INVALID)
375         {
376             packet_ptr -> nx_packet_vlan_priority = socket_ptr -> nx_tcp_socket_vlan_priority;
377         }
378 #endif /* NX_ENABLE_VLAN */
379 
380         /* If trace is enabled, insert this event into the trace buffer.  */
381         NX_TRACE_IN_LINE_INSERT(NX_TRACE_INTERNAL_TCP_RETRY, ip_ptr, socket_ptr, packet_ptr, socket_ptr -> nx_tcp_socket_timeout_retries, NX_TRACE_INTERNAL_EVENTS, 0, 0);
382 
383         /* Clear the queue next pointer.  */
384         packet_ptr -> nx_packet_queue_next =  NX_NULL;
385 
386         /* Yes, the driver has finished with the packet at the head of the
387            transmit sent list... so it can be sent again!  */
388 
389 #ifndef NX_DISABLE_IPV4
390         /* Is this an IPv4 connection? */
391         if (socket_ptr -> nx_tcp_socket_connect_ip.nxd_ip_version == NX_IP_VERSION_V4)
392         {
393             _nx_ip_packet_send(ip_ptr, packet_ptr,
394                                socket_ptr -> nx_tcp_socket_connect_ip.nxd_ip_address.v4,
395                                socket_ptr -> nx_tcp_socket_type_of_service,
396                                socket_ptr -> nx_tcp_socket_time_to_live, NX_IP_TCP,
397                                socket_ptr -> nx_tcp_socket_fragment_enable,
398                                socket_ptr -> nx_tcp_socket_next_hop_address);
399         }
400 #endif /* !NX_DISABLE_IPV4  */
401 
402 #ifdef FEATURE_NX_IPV6
403         if (socket_ptr -> nx_tcp_socket_connect_ip.nxd_ip_version == NX_IP_VERSION_V6)
404         {
405 
406             /* Handle for an IPv6 connection. */
407             /* Set the packet transmit interface before sending. */
408             packet_ptr -> nx_packet_address.nx_packet_ipv6_address_ptr = socket_ptr -> nx_tcp_socket_ipv6_addr;
409 
410             _nx_ipv6_packet_send(ip_ptr, packet_ptr, NX_PROTOCOL_TCP,
411                                  packet_ptr -> nx_packet_length, ip_ptr -> nx_ipv6_hop_limit,
412                                  socket_ptr -> nx_tcp_socket_ipv6_addr -> nxd_ipv6_address,
413                                  socket_ptr -> nx_tcp_socket_connect_ip.nxd_ip_address.v6);
414         }
415 #endif /* FEATURE_NX_IPV6 */
416 
417         /* Move to next packet. */
418         /* During fast recovery, only one packet is retransmitted at once. */
419         /* After a timeout, the sending data can be at most one SMSS. */
420         if ((next_ptr == (NX_PACKET *)NX_PACKET_ENQUEUED) ||
421             (socket_ptr -> nx_tcp_socket_fast_recovery == NX_TRUE))
422         {
423             break;
424         }
425         else
426         {
427             packet_ptr = next_ptr;
428         }
429     }
430 }
431 
432