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