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