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