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