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 Protocol (IP) */
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_ip.h"
29 #include "nx_ipv6.h"
30 #include "nx_packet.h"
31 #include "nx_icmpv6.h"
32
33 #ifdef FEATURE_NX_IPV6
34
35
36 /**************************************************************************/
37 /* */
38 /* FUNCTION RELEASE */
39 /* */
40 /* _nx_ipv6_packet_send PORTABLE C */
41 /* 6.1.8 */
42 /* AUTHOR */
43 /* */
44 /* Yuxin Zhou, Microsoft Corporation */
45 /* */
46 /* DESCRIPTION */
47 /* */
48 /* This function prepends an IP header and sends an IP packet to the */
49 /* appropriate link driver. Caller needs to fill in the correct */
50 /* source and destination addresses into the packet source and */
51 /* destination address. Caller also makes sure that the packet */
52 /* interface address is valid (not in tentative state), and source */
53 /* address is not unspecified e.g. NULL. */
54 /* */
55 /* INPUT */
56 /* */
57 /* ip_ptr Pointer to IP control block */
58 /* packet_ptr Pointer to packet to send */
59 /* protocol Protocol being encapsulated */
60 /* payload_size Size of the payload */
61 /* hop_limit Hop limit value to set in IP */
62 /* header. */
63 /* src_address Source address */
64 /* dest_address Destination address */
65 /* */
66 /* OUTPUT */
67 /* */
68 /* None */
69 /* */
70 /* CALLS */
71 /* */
72 /* _nx_ipv6_header_add Add IPv6 header */
73 /* _nx_packet_transmit_release Release transmit packet */
74 /* _nx_nd_cache_add_entry Add new entry to ND Cache */
75 /* IPv6_Address_Type Find IPv6 address type */
76 /* _nx_packet_copy Packet copy */
77 /* _nx_ip_packet_deferred_receive Places received packets in */
78 /* deferred packet queue */
79 /* _nx_icmpv6_send_ns Send neighbor solicitation */
80 /* _nxd_ipv6_search_onlink Find onlink match */
81 /* _nx_ipv6_fragment_processing Fragment processing */
82 /* (ip_link_driver) User supplied link driver */
83 /* */
84 /* CALLED BY */
85 /* */
86 /* NetX Source Code */
87 /* */
88 /* RELEASE HISTORY */
89 /* */
90 /* DATE NAME DESCRIPTION */
91 /* */
92 /* 05-19-2020 Yuxin Zhou Initial Version 6.0 */
93 /* 09-30-2020 Yuxin Zhou Modified comment(s), */
94 /* resulting in version 6.1 */
95 /* 08-02-2021 Yuxin Zhou Modified comment(s), and */
96 /* supported TCP/IP offload, */
97 /* resulting in version 6.1.8 */
98 /* */
99 /**************************************************************************/
_nx_ipv6_packet_send(NX_IP * ip_ptr,NX_PACKET * packet_ptr,ULONG protocol,ULONG payload_size,ULONG hop_limit,ULONG * src_address,ULONG * dest_address)100 VOID _nx_ipv6_packet_send(NX_IP *ip_ptr, NX_PACKET *packet_ptr,
101 ULONG protocol, ULONG payload_size, ULONG hop_limit,
102 ULONG *src_address, ULONG *dest_address)
103 {
104
105 UINT status = NX_SUCCESS;
106 ULONG address_type;
107 UINT next_hop_mtu;
108 ULONG fragment = NX_TRUE;
109 #ifdef NX_ENABLE_IPV6_PATH_MTU_DISCOVERY
110 NX_IPV6_DESTINATION_ENTRY *next_hop_dest_entry_ptr;
111 #endif /* NX_ENABLE_IPV6_PATH_MTU_DISCOVERY */
112 NX_IP_DRIVER driver_request;
113 NX_PACKET *remove_packet;
114 NX_PACKET *packet_copy;
115 UINT same_address;
116 NX_INTERFACE *if_ptr;
117 NX_IPV6_DESTINATION_ENTRY *dest_entry_ptr;
118
119 /*lint -e{644} suppress variable might not be initialized, since "packet_ptr" was initialized. */
120 if_ptr = packet_ptr -> nx_packet_address.nx_packet_ipv6_address_ptr -> nxd_ipv6_address_attached;
121
122 /* Interface can not be NULL. */
123 NX_ASSERT(if_ptr != NX_NULL);
124
125 #ifdef NX_ENABLE_TCPIP_OFFLOAD
126 if (if_ptr -> nx_interface_capability_flag & NX_INTERFACE_CAPABILITY_TCPIP_OFFLOAD)
127 {
128 #ifndef NX_DISABLE_IP_INFO
129
130 /* Increment the IP invalid packet error. */
131 ip_ptr -> nx_ip_invalid_transmit_packets++;
132 #endif
133
134 /* Ignore sending all packets for TCP/IP offload. Release the packet. */
135 _nx_packet_transmit_release(packet_ptr);
136
137 /* Return... nothing more can be done! */
138 return;
139 }
140 #endif /* NX_ENABLE_TCPIP_OFFLOAD */
141
142 /* Add IPv6 header. */
143 if (_nx_ipv6_header_add(ip_ptr, &packet_ptr, protocol, payload_size,
144 hop_limit, src_address, dest_address, &fragment) != NX_SUCCESS)
145 {
146
147 /* Failed to add header. */
148 return;
149 }
150
151 #ifdef NX_ENABLE_IP_PACKET_FILTER
152 /* Check if the IP packet filter is set. */
153 if (ip_ptr -> nx_ip_packet_filter)
154 {
155
156 /* Yes, call the IP packet filter routine. */
157 if (ip_ptr -> nx_ip_packet_filter((VOID *)(packet_ptr -> nx_packet_prepend_ptr),
158 NX_IP_PACKET_OUT) != NX_SUCCESS)
159 {
160
161 /* Drop the packet. */
162 _nx_packet_transmit_release(packet_ptr);
163 return;
164 }
165 }
166
167 /* Check if the IP packet filter extended is set. */
168 if (ip_ptr -> nx_ip_packet_filter_extended)
169 {
170
171 /* Yes, call the IP packet filter extended routine. */
172 if (ip_ptr -> nx_ip_packet_filter_extended(ip_ptr, packet_ptr, NX_IP_PACKET_OUT) != NX_SUCCESS)
173 {
174
175 /* Drop the packet. */
176 _nx_packet_transmit_release(packet_ptr);
177 return;
178 }
179 }
180 #endif /* NX_ENABLE_IP_PACKET_FILTER */
181
182 next_hop_mtu = if_ptr -> nx_interface_ip_mtu_size;
183 packet_ptr -> nx_packet_ip_header = packet_ptr -> nx_packet_prepend_ptr;
184
185 /* Check if the host is sending itself a packet. */
186 same_address = (UINT)CHECK_IPV6_ADDRESSES_SAME(dest_address, src_address);
187
188 /* If it is, consider this a loopback address. */
189 if (same_address == 1)
190 {
191
192 address_type = IPV6_ADDRESS_LOOPBACK;
193 }
194 else
195 {
196
197 /* Otherwise check if this packet sending to a known loopback address. */
198 address_type = IPv6_Address_Type(dest_address);
199 }
200
201 /* Handle the internal loopback case. */
202 if (address_type == IPV6_ADDRESS_LOOPBACK)
203 {
204
205 if (_nx_packet_copy(packet_ptr, &packet_copy, ip_ptr -> nx_ip_default_packet_pool, NX_NO_WAIT) == NX_SUCCESS)
206 {
207
208 #ifdef NX_ENABLE_INTERFACE_CAPABILITY
209
210 /* Compute checksum for upper layer protocol. */
211 /*lint -e{644} suppress variable might not be initialized, since "packet_copy" was initialized as long as return value is NX_SUCCESS. */
212 if (packet_copy -> nx_packet_interface_capability_flag)
213 {
214 _nx_ip_packet_checksum_compute(packet_copy);
215 }
216 #endif /* NX_ENABLE_INTERFACE_CAPABILITY */
217
218 /* Get the interface of copied packet. */
219 /*lint --e{644} suppress variable might not be initialized, since "packet_copy" was initialized as long as return value is NX_SUCCESS. */
220 packet_copy -> nx_packet_address.nx_packet_interface_ptr = packet_ptr -> nx_packet_address.nx_packet_ipv6_address_ptr -> nxd_ipv6_address_attached;
221
222 #ifndef NX_DISABLE_IP_INFO
223
224 /* Increment the IP packet sent count. */
225 ip_ptr -> nx_ip_total_packets_sent++;
226
227 /* Increment the IP bytes sent count. */
228 ip_ptr -> nx_ip_total_bytes_sent += packet_ptr -> nx_packet_length - (ULONG)sizeof(NX_IPV6_HEADER);
229 #endif
230
231 /* Send the packet to this IP's receive processing like it came in from the driver. */
232 _nx_ip_packet_deferred_receive(ip_ptr, packet_copy);
233 }
234 #ifndef NX_DISABLE_IP_INFO
235 else
236 {
237 /* Increment the IP send packets dropped count. */
238 ip_ptr -> nx_ip_send_packets_dropped++;
239
240 /* Increment the IP transmit resource error count. */
241 ip_ptr -> nx_ip_transmit_resource_errors++;
242 }
243 #endif
244 /* Release the transmit packet. */
245 _nx_packet_transmit_release(packet_ptr);
246 return;
247 }
248
249 /* Initial the driver request. */
250 driver_request.nx_ip_driver_ptr = ip_ptr;
251 driver_request.nx_ip_driver_command = NX_LINK_PACKET_SEND;
252 driver_request.nx_ip_driver_packet = packet_ptr;
253 driver_request.nx_ip_driver_interface = NX_NULL;
254
255 /* Determine if physical mapping is needed by this link driver. */
256 if (if_ptr -> nx_interface_address_mapping_needed)
257 {
258
259 /* Is this packet a multicast ? */
260 if ((dest_address[0] & (ULONG)0xFF000000) == (ULONG)0xFF000000)
261 {
262
263
264 /* Set up the driver request. */
265 driver_request.nx_ip_driver_physical_address_msw = 0x00003333;
266 driver_request.nx_ip_driver_physical_address_lsw = dest_address[3];
267 driver_request.nx_ip_driver_interface = if_ptr;
268
269 /* It is; is path MTU enabled? */
270 #ifdef NX_ENABLE_IPV6_PATH_MTU_DISCOVERY
271
272 /* It is. We will check the path MTU for the packet destination to
273 determine if we need to fragment the packet. */
274
275 /* Lookup the multicast destination in the destination table. */
276 status = _nx_icmpv6_dest_table_find(ip_ptr, dest_address, &dest_entry_ptr, 0, 0);
277
278 /* Did we find it in the table? */
279 /*lint -e{644} suppress variable might not be initialized, since "dest_entry_ptr" was initialized as long as status is NX_SUCCESS. */
280 if (status == NX_SUCCESS)
281 {
282 next_hop_mtu = dest_entry_ptr -> nx_ipv6_destination_entry_path_mtu;
283 }
284
285 #endif /* NX_ENABLE_IPV6_PATH_MTU_DISCOVERY */
286 }
287 else
288 {
289
290 /* Obtain MAC address */
291 ND_CACHE_ENTRY *NDCacheEntry = NX_NULL;
292 ULONG next_hop_address[4];
293
294 SET_UNSPECIFIED_ADDRESS(next_hop_address);
295
296 /* Lookup the packet destination in the destination table. */
297 status = _nx_icmpv6_dest_table_find(ip_ptr, dest_address, &dest_entry_ptr, 0, 0);
298
299 /* Was a matching entry found? */
300 if (status != NX_SUCCESS)
301 {
302
303 /* No; If the packet is either onlink or there is no default router,
304 just copy the packet destination address to the 'next hop' address. */
305
306 if (_nxd_ipv6_search_onlink(ip_ptr, dest_address))
307 {
308 COPY_IPV6_ADDRESS(dest_address, next_hop_address);
309
310 /* Add the next_hop in destination table. */
311 status = _nx_icmpv6_dest_table_add(ip_ptr, dest_address, &dest_entry_ptr,
312 next_hop_address, if_ptr -> nx_interface_ip_mtu_size,
313 NX_WAIT_FOREVER, packet_ptr -> nx_packet_address.nx_packet_ipv6_address_ptr);
314
315 /* Get the NDCacheEntry. */
316 if (status == NX_SUCCESS)
317 {
318 NDCacheEntry = dest_entry_ptr -> nx_ipv6_destination_entry_nd_entry;
319 }
320 }
321 /* Check whether or not we have a default router. */
322 /* Suppress cast of pointer to pointer, since it is necessary */
323 else if (_nxd_ipv6_router_lookup(ip_ptr, if_ptr, next_hop_address, /*lint -e{929}*/ (void **)&NDCacheEntry) == NX_SUCCESS)
324 {
325 /* Add the next_hop in destination table. */
326 status = _nx_icmpv6_dest_table_add(ip_ptr, dest_address, &dest_entry_ptr,
327 next_hop_address, if_ptr -> nx_interface_ip_mtu_size,
328 NX_WAIT_FOREVER, packet_ptr -> nx_packet_address.nx_packet_ipv6_address_ptr);
329
330 /* If the default router did not has a reachable ND_CACHE_ENTRY. Get the NDCacheEntry. */
331 /*lint -e{644} suppress variable might not be initialized, since "NDCacheEntry" was initialized in _nxd_ipv6_route_lookup. */
332 if ((status == NX_SUCCESS) && !NDCacheEntry)
333 {
334 NDCacheEntry = dest_entry_ptr -> nx_ipv6_destination_entry_nd_entry;
335 }
336 }
337
338 /* Destination table add failed. */
339 if (status)
340 {
341
342 /* Release the transmit packet. */
343 _nx_packet_transmit_release(packet_ptr);
344
345 /* Can't send it. */
346 return;
347 }
348 }
349 /* Find a valid destination cache, set the nd cache and next hop address. */
350 else
351 {
352
353 /* Get the destination and next hop address. */
354 NDCacheEntry = dest_entry_ptr -> nx_ipv6_destination_entry_nd_entry;
355 COPY_IPV6_ADDRESS(dest_entry_ptr -> nx_ipv6_destination_entry_next_hop, next_hop_address);
356 NX_ASSERT(NDCacheEntry -> nx_nd_cache_nd_status != ND_CACHE_STATE_INVALID);
357 }
358
359 /* According RFC2461 ch 7.3.3, as long as the entry is valid and not in INCOMPLETE state,
360 the IP layer should use the cached link layer address. */
361 if ((NDCacheEntry -> nx_nd_cache_nd_status >= ND_CACHE_STATE_REACHABLE) &&
362 (NDCacheEntry -> nx_nd_cache_nd_status <= ND_CACHE_STATE_PROBE))
363 {
364
365 UCHAR *mac_addr;
366
367 mac_addr = NDCacheEntry -> nx_nd_cache_mac_addr;
368
369 /* Assume we find the mac */
370 driver_request.nx_ip_driver_physical_address_msw = ((ULONG)mac_addr[0] << 8) | mac_addr[1];
371 driver_request.nx_ip_driver_physical_address_lsw =
372 ((ULONG)mac_addr[2] << 24) | ((ULONG)mac_addr[3] << 16) | ((ULONG)mac_addr[4] << 8) | mac_addr[5];
373 driver_request.nx_ip_driver_interface = if_ptr;
374
375 /* Check if path MTU Discovery is enabled first. */
376
377 #ifdef NX_ENABLE_IPV6_PATH_MTU_DISCOVERY
378
379 /* It is. To know if we need to fragment this packet we need the path MTU for the packet
380 destination. */
381
382 /* If this destination has a non null next hop, we need to ascertain the next hop MTU. */
383
384 /* Get the path MTU for the actual destination. */
385 next_hop_mtu = dest_entry_ptr -> nx_ipv6_destination_entry_path_mtu;
386
387
388 /* Find the next hop in the destination table. */
389 status = _nx_icmpv6_dest_table_find(ip_ptr, next_hop_address, &next_hop_dest_entry_ptr, 0, 0);
390
391 if (status == NX_SUCCESS)
392 {
393
394 /* Now compare the destination path MTU with the next hop path MTU*/
395 /*lint -e{644} suppress variable might not be initialized, since "next_hop_dest_entry_ptr" was initialized as long as status is NX_SUCCESS. */
396 if ((next_hop_dest_entry_ptr -> nx_ipv6_destination_entry_path_mtu > 0) &&
397 (next_hop_mtu > next_hop_dest_entry_ptr -> nx_ipv6_destination_entry_path_mtu))
398 {
399
400 /* Update the path mtu to reflect the next hop route. */
401 next_hop_mtu = next_hop_dest_entry_ptr -> nx_ipv6_destination_entry_path_mtu;
402 }
403 }
404
405 #endif /* NX_ENABLE_IPV6_PATH_MTU_DISCOVERY */
406
407 /* If the entry is in STALE state, move it to DELAY state. */
408 if (NDCacheEntry -> nx_nd_cache_nd_status == ND_CACHE_STATE_STALE)
409 {
410 NDCacheEntry -> nx_nd_cache_nd_status = ND_CACHE_STATE_DELAY;
411
412 /* Start the Delay first probe timer */
413 NDCacheEntry -> nx_nd_cache_timer_tick = NX_DELAY_FIRST_PROBE_TIME;
414 }
415 }
416 else
417 {
418
419 /* No MAC address was found in our cache table. Start the Neighbor Discovery (ND)
420 process to get it. */
421
422 /* Ensure the current packet's queue next pointer to NULL */
423 packet_ptr -> nx_packet_queue_next = NX_NULL;
424
425 /* Determine if the queue is empty. */
426 if (NDCacheEntry -> nx_nd_cache_packet_waiting_head == NX_NULL)
427 {
428 /* ICMPv6 is enabled */
429 if (ip_ptr -> nx_ip_icmpv6_packet_process)
430 {
431
432 /* Queue up this packet */
433 NDCacheEntry -> nx_nd_cache_packet_waiting_head = packet_ptr;
434 NDCacheEntry -> nx_nd_cache_packet_waiting_tail = packet_ptr;
435 NDCacheEntry -> nx_nd_cache_packet_waiting_queue_length = 1;
436
437 /* Add debug information. */
438 NX_PACKET_DEBUG(NX_PACKET_ND_WAITING_QUEUE, __LINE__, packet_ptr);
439
440 /* Set the outgoing address and interface to the cache entry. */
441 NDCacheEntry -> nx_nd_cache_outgoing_address = packet_ptr -> nx_packet_address.nx_packet_ipv6_address_ptr;
442 NDCacheEntry -> nx_nd_cache_interface_ptr = if_ptr;
443
444 /* Is this a new entry? */
445 if (NDCacheEntry -> nx_nd_cache_nd_status == ND_CACHE_STATE_CREATED)
446 {
447
448 /* Start Neighbor discovery process by advancing to the incomplete state. */
449 NDCacheEntry -> nx_nd_cache_nd_status = ND_CACHE_STATE_INCOMPLETE;
450 }
451
452 /* Note that the 2nd last parameter sendUnicast is set to Zero. In this case
453 the last arg NDCacheEntry is not being used in _nx_icmpv6_send_ns. */
454 _nx_icmpv6_send_ns(ip_ptr, next_hop_address,
455 1, packet_ptr -> nx_packet_address.nx_packet_ipv6_address_ptr, 0, NDCacheEntry);
456
457 NDCacheEntry -> nx_nd_cache_num_solicit = NX_MAX_MULTICAST_SOLICIT - 1;
458 NDCacheEntry -> nx_nd_cache_timer_tick = ip_ptr -> nx_ipv6_retrans_timer_ticks;
459 }
460 else
461 {
462
463 _nx_packet_transmit_release(packet_ptr);
464 #ifndef NX_DISABLE_IP_INFO
465
466 /* Increment the IP transmit resource error count. */
467 ip_ptr -> nx_ip_transmit_resource_errors++;
468
469 /* Increment the IP send packets dropped count. */
470 ip_ptr -> nx_ip_send_packets_dropped++;
471 #endif
472 }
473 return;
474 }
475
476 /* The ND process already started. Simply queue up this packet */
477 NDCacheEntry -> nx_nd_cache_packet_waiting_tail -> nx_packet_queue_next = packet_ptr;
478 NDCacheEntry -> nx_nd_cache_packet_waiting_tail = packet_ptr;
479 NDCacheEntry -> nx_nd_cache_packet_waiting_queue_length++;
480
481 /* Add debug information. */
482 NX_PACKET_DEBUG(NX_PACKET_ND_WAITING_QUEUE, __LINE__, packet_ptr);
483
484 /* Check if the number of packets enqueued exceeds the allowed number. */
485 if (NDCacheEntry -> nx_nd_cache_packet_waiting_queue_length > NX_ND_MAX_QUEUE_DEPTH)
486 {
487
488 /* Yes, so delete the first packet. */
489 remove_packet = NDCacheEntry -> nx_nd_cache_packet_waiting_head;
490
491 NDCacheEntry -> nx_nd_cache_packet_waiting_head = remove_packet -> nx_packet_queue_next;
492
493 /* Update the queued packet count for this cache entry. */
494 NDCacheEntry -> nx_nd_cache_packet_waiting_queue_length--;
495
496 _nx_packet_transmit_release(remove_packet);
497 #ifndef NX_DISABLE_IP_INFO
498 /* Increment the IP transmit resource error count. */
499 ip_ptr -> nx_ip_transmit_resource_errors++;
500
501 /* Increment the IP send packets dropped count. */
502 ip_ptr -> nx_ip_send_packets_dropped++;
503 #endif
504 }
505
506 return;
507 }
508 }
509 }
510 else
511 {
512
513 /* This IP instance does not require any IP-to-physical mapping. */
514 /* Build the driver request. */
515
516 driver_request.nx_ip_driver_physical_address_msw = 0;
517 driver_request.nx_ip_driver_physical_address_lsw = 0;
518 driver_request.nx_ip_driver_interface = if_ptr;
519 }
520
521 /* Does the packet payload exceed next hop MTU? */
522 if (packet_ptr -> nx_packet_length > next_hop_mtu)
523 {
524 #ifndef NX_DISABLE_FRAGMENTATION
525 #ifdef NX_IPSEC_ENABLE
526 /* Check the fragment status, transport mode SAs can not carry fragment, RFC 4301 page 66&88. */
527 /*lint -e{774} suppress boolean always evaluates to True, since the value fragment can changed when NX_IPSEC_ENABLE is defined. */
528 if (fragment == NX_TRUE)
529 {
530 #endif /* NX_IPSEC_ENABLE */
531
532 /* Yes; ok to fragment the packet payload. */
533 _nx_ipv6_fragment_process(&driver_request, next_hop_mtu);
534 #ifdef NX_IPSEC_ENABLE
535 }
536 else
537 {
538
539 #ifndef NX_DISABLE_IP_INFO
540
541 /* Increment the IP send packets dropped count. */
542 ip_ptr -> nx_ip_send_packets_dropped++;
543 #endif
544 /* Just release the packet. */
545 _nx_packet_transmit_release(packet_ptr);
546 }
547 #endif /* NX_IPSEC_ENABLE */
548
549 #else /* NX_DISABLE_FRAGMENTATION */
550
551 #ifndef NX_DISABLE_IP_INFO
552 /* Increment the IP send packets dropped count. */
553 ip_ptr -> nx_ip_send_packets_dropped++;
554 #endif
555 /* Just release the packet. */
556 _nx_packet_transmit_release(packet_ptr);
557 #endif /* NX_DISABLE_FRAGMENTATION */
558
559 /* This packet send is complete, just return. */
560 return;
561 }
562
563 /* The packet requires no fragmentation. Proceed with sending the packet. */
564
565 #ifndef NX_DISABLE_IP_INFO
566
567 /* Increment the IP packet sent count. */
568 ip_ptr -> nx_ip_total_packets_sent++;
569
570 /* Increment the IP bytes sent count. */
571 ip_ptr -> nx_ip_total_bytes_sent += packet_ptr -> nx_packet_length - (ULONG)sizeof(NX_IPV6_HEADER);
572 #endif
573
574 /* Add debug information. */
575 NX_PACKET_DEBUG(__FILE__, __LINE__, packet_ptr);
576
577 /* Driver entry must not be NULL. */
578 NX_ASSERT(if_ptr -> nx_interface_link_driver_entry != NX_NULL);
579
580 /* Send the IP packet out on the network via the attached driver. */
581 (if_ptr -> nx_interface_link_driver_entry)(&driver_request);
582 }
583
584 #endif /* FEATURE_NX_IPV6 */
585
586