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 /** Internet Control Message Protocol (ICMP) */
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_ipv6.h"
31 #include "nx_icmpv6.h"
32
33 #ifdef NX_IPSEC_ENABLE
34 #include "nx_ipsec.h"
35 #endif /* NX_IPSEC_ENABLE */
36
37
38 #ifdef FEATURE_NX_IPV6
39
40
41 /**************************************************************************/
42 /* */
43 /* FUNCTION RELEASE */
44 /* */
45 /* _nx_icmpv6_process_ns PORTABLE C */
46 /* 6.3.0 */
47 /* AUTHOR */
48 /* */
49 /* Yuxin Zhou, Microsoft Corporation */
50 /* */
51 /* DESCRIPTION */
52 /* */
53 /* This internal function processes an incoming neighbor solicitation */
54 /* message. In response to a valid NS message, it also sends out */
55 /* a neighbor solicitation, and updates the Neighbor Cache. */
56 /* */
57 /* INPUT */
58 /* */
59 /* ip_ptr IP stack instance */
60 /* packet_ptr The echo reply packet */
61 /* */
62 /* OUTPUT */
63 /* */
64 /* None */
65 /* */
66 /* CALLS */
67 /* */
68 /* tx_mutex_get Obtain exclusive lock (on table) */
69 /* tx_mutex_put Release exclusive lock */
70 /* _nx_packet_release Release packet back to pool */
71 /* _nx_ipv6_packet_send Send IPv6 packet to remote host */
72 /* _nx_nd_cache_add Add entry to ND cache table */
73 /* _nx_nd_cache_find_entry Find ND cache entry by IP address*/
74 /* _nx_icmpv6_send_queued_packets Send packets queued waiting for */
75 /* physical mapping */
76 /* _nx_icmpv6_validate_neighbor_message */
77 /* Validate received ICMPv6 packet */
78 /* _nx_ip_checksum_compute Compute NS packet ICMP checksum */
79 /* */
80 /* CALLED BY */
81 /* */
82 /* _nx_icmpv6_process Main ICMPv6 processor */
83 /* */
84 /* RELEASE HISTORY */
85 /* */
86 /* DATE NAME DESCRIPTION */
87 /* */
88 /* 05-19-2020 Yuxin Zhou Initial Version 6.0 */
89 /* 09-30-2020 Yuxin Zhou Modified comment(s), */
90 /* resulting in version 6.1 */
91 /* 10-31-2023 Bo Chen Modified comment(s), improved */
92 /* packet length verification, */
93 /* resulting in version 6.3.0 */
94 /* */
95 /**************************************************************************/
_nx_icmpv6_process_ns(NX_IP * ip_ptr,NX_PACKET * packet_ptr)96 VOID _nx_icmpv6_process_ns(NX_IP *ip_ptr, NX_PACKET *packet_ptr)
97 {
98
99 NX_ICMPV6_ND *nd_ptr;
100 NX_ICMPV6_OPTION *option_ptr;
101 USHORT *mac_addr;
102 UINT source_unspecified;
103 UINT error;
104 UINT option_length;
105 NX_ICMPV6_HEADER *header_ptr;
106 NX_IPV6_HEADER *ipv6_header;
107 UINT SLLA_changed = NX_FALSE;
108 UINT i;
109 NXD_IPV6_ADDRESS *interface_addr;
110 #if defined(NX_DISABLE_ICMPV6_TX_CHECKSUM) || defined(NX_ENABLE_INTERFACE_CAPABILITY) || defined(NX_IPSEC_ENABLE)
111 UINT compute_checksum = 1;
112 #endif /* defined(NX_DISABLE_ICMPV6_TX_CHECKSUM) || defined(NX_ENABLE_INTERFACE_CAPABILITY) || defined(NX_IPSEC_ENABLE) */
113 ULONG dest_address[4];
114
115
116 /* Initialize local variable: assume source address is specified. */
117 source_unspecified = NX_FALSE;
118
119 /* Assume there is no error. */
120 error = 0;
121
122 /* Get a pointer to the ICMP message header. */
123 /*lint -e{927} -e{826} suppress cast of pointer to pointer, since it is necessary */
124 header_ptr = (NX_ICMPV6_HEADER *)packet_ptr -> nx_packet_prepend_ptr;
125
126 /* Get a pointer to the IPv6 header. */
127 /*lint -e{927} -e{826} suppress cast of pointer to pointer, since it is necessary */
128 ipv6_header = (NX_IPV6_HEADER *)packet_ptr -> nx_packet_ip_header;
129
130 #ifndef NX_DISABLE_RX_SIZE_CHECKING
131 /* Check packet length is at least sizeof(NX_ICMPV6_ND). */
132 if ((packet_ptr -> nx_packet_length < sizeof(NX_ICMPV6_ND))
133 #ifndef NX_DISABLE_PACKET_CHAIN
134 || (packet_ptr -> nx_packet_next) /* Ignore chained packet. */
135 #endif /* NX_DISABLE_PACKET_CHAIN */
136 )
137 {
138 #ifndef NX_DISABLE_ICMP_INFO
139
140 /* Increment the ICMP invalid packet error. */
141 ip_ptr -> nx_ip_icmp_invalid_packets++;
142 #endif /* NX_DISABLE_ICMP_INFO */
143
144 /* Release the packet and we are done. */
145 _nx_packet_release(packet_ptr);
146 return;
147 }
148 #endif /* NX_DISABLE_RX_SIZE_CHECKING */
149
150 /* Get a pointer to the Neighbor Discovery message. */
151 /*lint -e{929} suppress cast of pointer to pointer, since it is necessary */
152 nd_ptr = (NX_ICMPV6_ND *)header_ptr;
153
154 /* Convert target address to host byte order. */
155 NX_IPV6_ADDRESS_CHANGE_ENDIAN(nd_ptr -> nx_icmpv6_nd_targetAddress);
156
157 /* Convert flag field to host byte order. */
158 NX_CHANGE_ULONG_ENDIAN(nd_ptr -> nx_icmpv6_nd_flag);
159
160
161 /* Validate the packet. */
162 if (_nx_icmpv6_validate_neighbor_message(packet_ptr) != NX_SUCCESS)
163 {
164 error = 1;
165 }
166
167 /* Find whether or not sender is unspecified. If sender is unspecified,
168 the sender is performing DAD process. */
169 if (CHECK_UNSPECIFIED_ADDRESS(ipv6_header -> nx_ip_header_source_ip))
170 {
171
172 /* Mark the packet source as nonsolicited. */
173 source_unspecified = NX_TRUE;
174 }
175
176 /* Find the appropriate interface to send the packet out on, based
177 on the destination address. */
178
179 /* Get a pointer to the first ipv6 address in the interface address list. */
180 interface_addr = packet_ptr -> nx_packet_address.nx_packet_ipv6_address_ptr;
181
182 if (!error)
183 {
184
185 /* Loop to match IP addresses. */
186 while (interface_addr != NX_NULL)
187 {
188
189 /* Does the current address in the IP interface list match the one in the
190 ND message header? */
191 if ((CHECK_IPV6_ADDRESSES_SAME(interface_addr -> nxd_ipv6_address,
192 nd_ptr -> nx_icmpv6_nd_targetAddress)))
193 {
194
195 /* We're done matching. */
196 break;
197 }
198
199 /* Get next IPv6 address in the interface address list. */
200 interface_addr = interface_addr -> nxd_ipv6_address_next;
201 }
202 }
203
204 if (error || (interface_addr == NX_NULL))
205 {
206
207 #ifndef NX_DISABLE_ICMP_INFO
208
209 /* Increment the ICMP invalid packet error. */
210 ip_ptr -> nx_ip_icmp_invalid_packets++;
211 #endif /* NX_DISABLE_ICMP_INFO */
212
213 /* An error occurred. Release the packet. */
214 _nx_packet_release(packet_ptr);
215
216 return;
217 }
218
219 /* Have find a valid address. */
220 packet_ptr -> nx_packet_address.nx_packet_ipv6_address_ptr = interface_addr;
221
222 /*
223 * Once we get here, we have two cases:
224 * (1) Sender is in the DAD process
225 * (2) Sender wants to find our MAC address.
226 *
227 * So first, we need to find out whether or not the source IP address is
228 * the unspecified address.
229 */
230
231 if (source_unspecified == NX_TRUE)
232 {
233
234 /* The sender is in DAD process. */
235
236 #ifndef NX_DISABLE_IPV6_DAD
237 /* The sender is doing a DAD on the same address as we have... */
238 if (interface_addr -> nxd_ipv6_address_state == NX_IPV6_ADDR_STATE_TENTATIVE)
239 {
240
241 /* Our interface address is in tentative state. Therefore interface
242 address is also invalid. */
243 _nx_icmpv6_DAD_failure(ip_ptr, interface_addr);
244
245 _nx_packet_release(packet_ptr);
246 return;
247 }
248 #endif
249 /* Our address state is not in tentative mode. That means
250 we have a valid address. In this case, we should send a response */
251 }
252
253 /* Get a pointer to the ICMPv6 options. */
254 /*lint -e{923} suppress cast between pointer and ULONG, since it is necessary */
255 option_ptr = (NX_ICMPV6_OPTION *)NX_UCHAR_POINTER_ADD(header_ptr, sizeof(NX_ICMPV6_ND));
256
257 /* We'll need to keep track of option data to parse the options. */
258 option_length = (UINT)packet_ptr -> nx_packet_length - (UINT)sizeof(NX_ICMPV6_ND);
259
260 /* Walk through the ICMPv6 options, if any. */
261 while (option_length > 0)
262 {
263
264 /* Handle the source link-layer address option for a solicited NS request. */
265 if (option_ptr -> nx_icmpv6_option_type == ICMPV6_OPTION_TYPE_SRC_LINK_ADDR)
266 {
267
268 ND_CACHE_ENTRY *nd_entry;
269 UINT status;
270
271
272 /* At this point, the source address has been verified to be
273 valid (not unspecified.) Therefore we should add
274 the source link layer address to our neighbor cache. */
275
276 /* Is the source IP address is not in our ND cache? */
277
278 status = _nx_nd_cache_find_entry(ip_ptr, ipv6_header -> nx_ip_header_source_ip, &nd_entry);
279
280 if (status != NX_SUCCESS)
281 {
282
283 /* Yes, this NS fills in the mac address so the LLA is changed. */
284 SLLA_changed = NX_TRUE;
285
286 /* No, so we create a cache entry. */
287 /*lint -e{929} suppress cast from pointer to pointer, since it is necessary */
288 /*lint -e{826} suppress cast of pointer to pointer, since it is necessary */
289 /*lint -e{740} suppress unusual cast of pointer, since it is necessary */
290 _nx_nd_cache_add(ip_ptr, ipv6_header -> nx_ip_header_source_ip,
291 interface_addr -> nxd_ipv6_address_attached,
292 (CHAR *)&option_ptr -> nx_icmpv6_option_data, 0, ND_CACHE_STATE_STALE,
293 interface_addr, &nd_entry);
294 }
295 else
296 {
297
298 /* Entry already exists. If the mac address is the same, do not update the entry. Otherwise,
299 update the entry and set the state to STALE (RFC2461 7.2.3) */
300 ULONG mac_msw, mac_lsw, new_msw, new_lsw;
301
302 /*lint -e{928} suppress cast from pointer to pointer, since it is necessary */
303 UCHAR *new_mac = (UCHAR *)&option_ptr -> nx_icmpv6_option_data;
304
305 /*lint -e{644} suppress variable might not be initialized, since "nd_entry" was initialized in _nx_nd_cache_find_entry. */
306 mac_msw = ((ULONG)(nd_entry -> nx_nd_cache_mac_addr[0]) << 8) | (nd_entry -> nx_nd_cache_mac_addr[1]);
307 mac_lsw = ((ULONG)(nd_entry -> nx_nd_cache_mac_addr[2]) << 24) | ((ULONG)(nd_entry -> nx_nd_cache_mac_addr[3]) << 16) |
308 ((ULONG)(nd_entry -> nx_nd_cache_mac_addr[4]) << 8) | nd_entry -> nx_nd_cache_mac_addr[5];
309 new_msw = ((ULONG)(new_mac[0]) << 8) | (new_mac[1]);
310 new_lsw = ((ULONG)(new_mac[2]) << 24) | ((ULONG)(new_mac[3]) << 16) | ((ULONG)(new_mac[4]) << 8) | new_mac[5]; /* lgtm[cpp/overflow-buffer] */
311 if ((mac_msw != new_msw) || (mac_lsw != new_lsw)) /* If the new MAC is different from what we have in the table. */
312 {
313
314 /* This NS changes the cache entry mac address, so the LLA is changed. */
315 SLLA_changed = NX_TRUE;
316
317 /* Set the mac address. */
318 for (i = 0; i < 6; i++)
319 {
320 nd_entry -> nx_nd_cache_mac_addr[i] = new_mac[i];
321 }
322
323 /* Set the state to STALE. */
324 nd_entry -> nx_nd_cache_nd_status = ND_CACHE_STATE_STALE;
325
326 /* Set the interface. */
327 nd_entry -> nx_nd_cache_interface_ptr = interface_addr -> nxd_ipv6_address_attached;
328 }
329
330 if (nd_entry -> nx_nd_cache_packet_waiting_head) /* There are packets waiting to be transmitted */
331 {
332
333 /* Ok to transmit the packets now. */
334 _nx_icmpv6_send_queued_packets(ip_ptr, nd_entry);
335 }
336 }
337 }
338
339 option_length -= ((UINT)(option_ptr -> nx_icmpv6_option_length) << 3);
340
341 /*lint -e{923} suppress cast between pointer and ULONG, since it is necessary */
342 option_ptr = (NX_ICMPV6_OPTION *)NX_UCHAR_POINTER_ADD(option_ptr, ((option_ptr -> nx_icmpv6_option_length) << 3));
343 }
344
345 /* We may need to reset a reachable timer if there is no new LLA involved.
346 Did we change the cache LLA? */
347 if (SLLA_changed == NX_FALSE)
348 {
349
350 /* No, so if this cache state is REACHABLE, check if we should
351 reset the timer tick after receiving a NS packet from the neighbor.
352 RFC 2461 7.2.3. */
353
354 ND_CACHE_ENTRY *nd_entry;
355
356 /* Verify the source of the NS packet in our ND cache, in case
357 we have not already done so. */
358 if (_nx_nd_cache_find_entry(ip_ptr, ipv6_header -> nx_ip_header_source_ip, &nd_entry) == NX_SUCCESS)
359 {
360
361 /* Yes, is it currently in a reachable state? */
362 /*lint -e{644} suppress variable might not be initialized, since "nd_entry" was initialized in _nx_nd_cache_find_entry. */
363 if (nd_entry -> nx_nd_cache_nd_status == ND_CACHE_STATE_REACHABLE)
364 {
365 /* Ok to update the timer. */
366 nd_entry -> nx_nd_cache_timer_tick = ip_ptr -> nx_ipv6_reachable_timer;
367 }
368 }
369 }
370
371 /*
372 * A valid NS has been received. Prepare a Neighbor Advertisement packet
373 * according to RFC2461 section 7.2.4.
374 *
375 * Adjust the packet, eliminate the option part. At this point, the packet prepend_ptr points to the
376 * beginning of the ICMP message. The size of the ICMP message includes ICMP NA and the target
377 * link layer address option field (8 bytes).
378 */
379 packet_ptr -> nx_packet_append_ptr =
380 packet_ptr -> nx_packet_prepend_ptr + sizeof(NX_ICMPV6_ND) + 8;
381
382 packet_ptr -> nx_packet_length = sizeof(NX_ICMPV6_ND) + 8;
383
384 if (source_unspecified == NX_TRUE)
385 {
386
387 /* Response to an unsolicited NS: clear the S bit.*/
388 nd_ptr -> nx_icmpv6_nd_flag = (0x20000000);
389 }
390 else
391 {
392 /* Response to a normal NS: set the S bit.*/
393 nd_ptr -> nx_icmpv6_nd_flag = (0x60000000);
394 }
395
396 NX_CHANGE_ULONG_ENDIAN(nd_ptr -> nx_icmpv6_nd_flag);
397
398 /* nd_ptr -> targetAddress has been converted to host byte order.
399 We need to convert it back to network byte order. */
400 NX_IPV6_ADDRESS_CHANGE_ENDIAN(nd_ptr -> nx_icmpv6_nd_targetAddress);
401
402 header_ptr -> nx_icmpv6_header_type = NX_ICMPV6_NEIGHBOR_ADVERTISEMENT_TYPE;
403 header_ptr -> nx_icmpv6_header_code = 0;
404 header_ptr -> nx_icmpv6_header_checksum = 0;
405
406 if (source_unspecified == NX_TRUE)
407 {
408
409 /* Sender uses unspecified address. So we send NA to all node multicast address. */
410 /* RFC2461 7.2.4 */
411 dest_address[0] = 0xFF020000;
412 dest_address[1] = 0;
413 dest_address[2] = 0;
414 dest_address[3] = 0x00000001;
415 }
416 else
417 {
418
419 /* Set the packet destination IP address */
420 COPY_IPV6_ADDRESS(ipv6_header -> nx_ip_header_source_ip,
421 dest_address);
422 }
423
424 /*
425 Fill in the options. Since we are using the same packet,
426 the option_ptr is already pointing to the option field.
427 */
428 /*lint -e{923} suppress cast between pointer and ULONG, since it is necessary */
429 option_ptr = (NX_ICMPV6_OPTION *)NX_UCHAR_POINTER_ADD(header_ptr, sizeof(NX_ICMPV6_ND));
430 option_ptr -> nx_icmpv6_option_type = ICMPV6_OPTION_TYPE_TRG_LINK_ADDR;
431 option_ptr -> nx_icmpv6_option_length = 1;
432 mac_addr = &option_ptr -> nx_icmpv6_option_data;
433
434 mac_addr[0] = (USHORT)(interface_addr -> nxd_ipv6_address_attached -> nx_interface_physical_address_msw);
435 mac_addr[1] = (USHORT)((interface_addr -> nxd_ipv6_address_attached -> nx_interface_physical_address_lsw & 0xFFFF0000) >> 16); /* lgtm[cpp/overflow-buffer] */
436 mac_addr[2] = (USHORT)(interface_addr -> nxd_ipv6_address_attached -> nx_interface_physical_address_lsw & 0x0000FFFF); /* lgtm[cpp/overflow-buffer] */
437
438 NX_CHANGE_USHORT_ENDIAN(mac_addr[0]);
439 NX_CHANGE_USHORT_ENDIAN(mac_addr[1]); /* lgtm[cpp/overflow-buffer] */
440 NX_CHANGE_USHORT_ENDIAN(mac_addr[2]); /* lgtm[cpp/overflow-buffer] */
441
442 #ifdef NX_DISABLE_ICMPV6_TX_CHECKSUM
443 compute_checksum = 0;
444 #endif /* NX_DISABLE_ICMPV6_TX_CHECKSUM */
445
446 #ifdef NX_ENABLE_INTERFACE_CAPABILITY
447 if (interface_addr -> nxd_ipv6_address_attached -> nx_interface_capability_flag & NX_INTERFACE_CAPABILITY_ICMPV6_TX_CHECKSUM)
448 {
449 compute_checksum = 0;
450 }
451 #endif /* NX_ENABLE_INTERFACE_CAPABILITY */
452 #if defined(NX_DISABLE_ICMPV6_TX_CHECKSUM) || defined(NX_ENABLE_INTERFACE_CAPABILITY) || defined(NX_IPSEC_ENABLE)
453 if (compute_checksum)
454 #endif /* defined(NX_DISABLE_ICMPV6_TX_CHECKSUM) || defined(NX_ENABLE_INTERFACE_CAPABILITY) || defined(NX_IPSEC_ENABLE) */
455 {
456
457 /* Compute the checksum. */
458 header_ptr -> nx_icmpv6_header_checksum =
459 _nx_ip_checksum_compute(packet_ptr, NX_PROTOCOL_ICMPV6,
460 (UINT)packet_ptr -> nx_packet_length,
461 interface_addr -> nxd_ipv6_address,
462 dest_address);
463
464 /* Write the checksum to the ICMPv6 header. */
465 header_ptr -> nx_icmpv6_header_checksum = (USHORT)(~(header_ptr -> nx_icmpv6_header_checksum));
466
467 NX_CHANGE_USHORT_ENDIAN(header_ptr -> nx_icmpv6_header_checksum);
468 }
469 #ifdef NX_ENABLE_INTERFACE_CAPABILITY
470 else
471 {
472 packet_ptr -> nx_packet_interface_capability_flag |= NX_INTERFACE_CAPABILITY_ICMPV6_TX_CHECKSUM;
473 }
474 #endif /* NX_ENABLE_INTERFACE_CAPABILITY */
475 /* Send out the NA reply. */
476 _nx_ipv6_packet_send(ip_ptr, packet_ptr,
477 NX_PROTOCOL_ICMPV6,
478 packet_ptr -> nx_packet_length,
479 255 /* NA message must have hop limit 255 */,
480 interface_addr -> nxd_ipv6_address,
481 dest_address);
482
483 /* (Let the driver release the packet.) */
484 return;
485 }
486
487 #endif /* FEATURE_NX_IPV6 */
488
489