2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS TCP/IP protocol driver
4 * FILE: network/receive.c
5 * PURPOSE: Internet Protocol receive routines
6 * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
7 * NOTES: The IP datagram reassembly algorithm is taken from
10 * CSH 01/08-2000 Created
15 LIST_ENTRY ReassemblyListHead
;
16 KSPIN_LOCK ReassemblyListLock
;
17 NPAGED_LOOKASIDE_LIST IPDRList
;
18 NPAGED_LOOKASIDE_LIST IPFragmentList
;
19 NPAGED_LOOKASIDE_LIST IPHoleList
;
21 PIPDATAGRAM_HOLE
CreateHoleDescriptor(
25 * FUNCTION: Returns a pointer to a IP datagram hole descriptor
27 * First = Offset of first octet of the hole
28 * Last = Offset of last octet of the hole
30 * Pointer to descriptor, NULL if there was not enough free
34 PIPDATAGRAM_HOLE Hole
;
36 TI_DbgPrint(DEBUG_IP
, ("Called. First (%d) Last (%d).\n", First
, Last
));
38 Hole
= ExAllocateFromNPagedLookasideList(&IPHoleList
);
40 TI_DbgPrint(MIN_TRACE
, ("Insufficient resources.\n"));
47 TI_DbgPrint(DEBUG_IP
, ("Returning hole descriptor at (0x%X).\n", Hole
));
54 PIPDATAGRAM_REASSEMBLY IPDR
)
56 * FUNCTION: Frees an IP datagram reassembly structure
58 * IPDR = Pointer to IP datagram reassembly structure
61 PLIST_ENTRY CurrentEntry
;
62 PLIST_ENTRY NextEntry
;
63 PIPDATAGRAM_HOLE CurrentH
;
64 PIP_FRAGMENT CurrentF
;
66 TI_DbgPrint(DEBUG_IP
, ("Freeing IP datagram reassembly descriptor (0x%X).\n", IPDR
));
68 /* Free all descriptors */
69 CurrentEntry
= IPDR
->HoleListHead
.Flink
;
70 while (CurrentEntry
!= &IPDR
->HoleListHead
) {
71 NextEntry
= CurrentEntry
->Flink
;
72 CurrentH
= CONTAINING_RECORD(CurrentEntry
, IPDATAGRAM_HOLE
, ListEntry
);
73 /* Unlink it from the list */
74 RemoveEntryList(CurrentEntry
);
76 TI_DbgPrint(DEBUG_IP
, ("Freeing hole descriptor at (0x%X).\n", CurrentH
));
78 /* And free the hole descriptor */
79 ExFreeToNPagedLookasideList(&IPHoleList
, CurrentH
);
81 CurrentEntry
= NextEntry
;
84 /* Free all fragments */
85 CurrentEntry
= IPDR
->FragmentListHead
.Flink
;
86 while (CurrentEntry
!= &IPDR
->FragmentListHead
) {
87 NextEntry
= CurrentEntry
->Flink
;
88 CurrentF
= CONTAINING_RECORD(CurrentEntry
, IP_FRAGMENT
, ListEntry
);
89 /* Unlink it from the list */
90 RemoveEntryList(CurrentEntry
);
92 TI_DbgPrint(DEBUG_IP
, ("Freeing fragment data at (0x%X).\n", CurrentF
->Data
));
94 /* Free the fragment data buffer */
95 ExFreePoolWithTag(CurrentF
->Data
, FRAGMENT_DATA_TAG
);
97 TI_DbgPrint(DEBUG_IP
, ("Freeing fragment at (0x%X).\n", CurrentF
));
99 /* And free the fragment descriptor */
100 ExFreeToNPagedLookasideList(&IPFragmentList
, CurrentF
);
101 CurrentEntry
= NextEntry
;
104 TI_DbgPrint(DEBUG_IP
, ("Freeing IPDR data at (0x%X).\n", IPDR
));
106 ExFreeToNPagedLookasideList(&IPDRList
, IPDR
);
111 PIPDATAGRAM_REASSEMBLY IPDR
)
113 * FUNCTION: Removes an IP datagram reassembly structure from the global list
115 * IPDR = Pointer to IP datagram reassembly structure
120 TI_DbgPrint(DEBUG_IP
, ("Removing IPDR at (0x%X).\n", IPDR
));
122 TcpipAcquireSpinLock(&ReassemblyListLock
, &OldIrql
);
123 RemoveEntryList(&IPDR
->ListEntry
);
124 TcpipReleaseSpinLock(&ReassemblyListLock
, OldIrql
);
128 PIPDATAGRAM_REASSEMBLY
GetReassemblyInfo(
131 * FUNCTION: Returns a pointer to an IP datagram reassembly structure
133 * IPPacket = Pointer to IP packet
135 * A datagram is identified by four paramters, which are
136 * Source and destination address, protocol number and
137 * identification number
141 PLIST_ENTRY CurrentEntry
;
142 PIPDATAGRAM_REASSEMBLY Current
;
143 PIPv4_HEADER Header
= (PIPv4_HEADER
)IPPacket
->Header
;
145 TI_DbgPrint(DEBUG_IP
, ("Searching for IPDR for IP packet at (0x%X).\n", IPPacket
));
147 TcpipAcquireSpinLock(&ReassemblyListLock
, &OldIrql
);
149 /* FIXME: Assume IPv4 */
151 CurrentEntry
= ReassemblyListHead
.Flink
;
152 while (CurrentEntry
!= &ReassemblyListHead
) {
153 Current
= CONTAINING_RECORD(CurrentEntry
, IPDATAGRAM_REASSEMBLY
, ListEntry
);
154 if (AddrIsEqual(&IPPacket
->SrcAddr
, &Current
->SrcAddr
) &&
155 (Header
->Id
== Current
->Id
) &&
156 (Header
->Protocol
== Current
->Protocol
) &&
157 (AddrIsEqual(&IPPacket
->DstAddr
, &Current
->DstAddr
))) {
158 TcpipReleaseSpinLock(&ReassemblyListLock
, OldIrql
);
162 CurrentEntry
= CurrentEntry
->Flink
;
165 TcpipReleaseSpinLock(&ReassemblyListLock
, OldIrql
);
174 PIPDATAGRAM_REASSEMBLY IPDR
)
176 * FUNCTION: Reassembles an IP datagram
178 * IPDR = Pointer to IP datagram reassembly structure
180 * This routine concatenates fragments into a complete IP datagram.
181 * The lock is held when this routine is called
183 * Pointer to IP packet, NULL if there was not enough free resources
185 * At this point, header is expected to point to the IP header
188 PLIST_ENTRY CurrentEntry
;
189 PIP_FRAGMENT Current
;
192 TI_DbgPrint(DEBUG_IP
, ("Reassembling datagram from IPDR at (0x%X).\n", IPDR
));
193 TI_DbgPrint(DEBUG_IP
, ("IPDR->HeaderSize = %d\n", IPDR
->HeaderSize
));
194 TI_DbgPrint(DEBUG_IP
, ("IPDR->DataSize = %d\n", IPDR
->DataSize
));
196 TI_DbgPrint(DEBUG_IP
, ("Fragment header:\n"));
197 //OskitDumpBuffer((PCHAR)IPDR->IPv4Header, IPDR->HeaderSize);
199 IPPacket
->TotalSize
= IPDR
->HeaderSize
+ IPDR
->DataSize
;
200 IPPacket
->ContigSize
= IPPacket
->TotalSize
;
201 IPPacket
->HeaderSize
= IPDR
->HeaderSize
;
202 /*IPPacket->Position = IPDR->HeaderSize;*/
204 RtlCopyMemory(&IPPacket
->SrcAddr
, &IPDR
->SrcAddr
, sizeof(IP_ADDRESS
));
205 RtlCopyMemory(&IPPacket
->DstAddr
, &IPDR
->DstAddr
, sizeof(IP_ADDRESS
));
207 /* Allocate space for full IP datagram */
208 IPPacket
->Header
= ExAllocatePoolWithTag(NonPagedPool
, IPPacket
->TotalSize
, PACKET_BUFFER_TAG
);
209 if (!IPPacket
->Header
) {
210 TI_DbgPrint(MIN_TRACE
, ("Insufficient resources.\n"));
211 (*IPPacket
->Free
)(IPPacket
);
215 /* Copy the header into the buffer */
216 RtlCopyMemory(IPPacket
->Header
, &IPDR
->IPv4Header
, IPDR
->HeaderSize
);
218 Data
= (PVOID
)((ULONG_PTR
)IPPacket
->Header
+ IPDR
->HeaderSize
);
219 IPPacket
->Data
= Data
;
221 /* Copy data from all fragments into buffer */
222 CurrentEntry
= IPDR
->FragmentListHead
.Flink
;
223 while (CurrentEntry
!= &IPDR
->FragmentListHead
) {
224 Current
= CONTAINING_RECORD(CurrentEntry
, IP_FRAGMENT
, ListEntry
);
226 TI_DbgPrint(DEBUG_IP
, ("Copying (%d) bytes of fragment data from (0x%X) to offset (%d).\n",
227 Current
->Size
, Data
, Current
->Offset
));
228 /* Copy fragment data to the destination buffer at the correct offset */
229 RtlCopyMemory((PVOID
)((ULONG_PTR
)Data
+ Current
->Offset
),
232 //OskitDumpBuffer( Data, Current->Offset + Current->Size );
233 CurrentEntry
= CurrentEntry
->Flink
;
240 __inline VOID
Cleanup(
243 PIPDATAGRAM_REASSEMBLY IPDR
)
245 * FUNCTION: Performs cleaning operations on errors
247 * Lock = Pointer to spin lock to be released
248 * OldIrql = Value of IRQL when spin lock was acquired
249 * IPDR = Pointer to IP datagram reassembly structure to free
250 * Buffer = Optional pointer to a buffer to free
253 TI_DbgPrint(MIN_TRACE
, ("Insufficient resources.\n"));
255 TcpipReleaseSpinLock(Lock
, OldIrql
);
261 VOID
ProcessFragment(
265 * FUNCTION: Processes an IP datagram or fragment
267 * IF = Pointer to IP interface packet was receive on
268 * IPPacket = Pointer to IP packet
270 * This routine reassembles fragments and, if a whole datagram can
271 * be assembled, passes the datagram on to the IP protocol dispatcher
275 PIPDATAGRAM_REASSEMBLY IPDR
;
276 PLIST_ENTRY CurrentEntry
;
277 PIPDATAGRAM_HOLE Hole
, NewHole
;
280 BOOLEAN MoreFragments
;
281 PIPv4_HEADER IPv4Header
;
283 PIP_FRAGMENT Fragment
;
286 /* FIXME: Assume IPv4 */
288 IPv4Header
= (PIPv4_HEADER
)IPPacket
->Header
;
290 /* Check if we already have an reassembly structure for this datagram */
291 IPDR
= GetReassemblyInfo(IPPacket
);
293 TI_DbgPrint(DEBUG_IP
, ("Continueing assembly.\n"));
294 /* We have a reassembly structure */
295 TcpipAcquireSpinLock(&IPDR
->Lock
, &OldIrql
);
297 /* Reset the timeout since we received a fragment */
298 IPDR
->TimeoutCount
= 0;
300 TI_DbgPrint(DEBUG_IP
, ("Starting new assembly.\n"));
302 /* We don't have a reassembly structure, create one */
303 IPDR
= ExAllocateFromNPagedLookasideList(&IPDRList
);
305 /* We don't have the resources to process this packet, discard it */
308 /* Create a descriptor spanning from zero to infinity.
309 Actually, we use a value slightly greater than the
310 maximum number of octets an IP datagram can contain */
311 Hole
= CreateHoleDescriptor(0, 65536);
313 /* We don't have the resources to process this packet, discard it */
314 ExFreeToNPagedLookasideList(&IPDRList
, IPDR
);
317 AddrInitIPv4(&IPDR
->SrcAddr
, IPv4Header
->SrcAddr
);
318 AddrInitIPv4(&IPDR
->DstAddr
, IPv4Header
->DstAddr
);
319 IPDR
->Id
= IPv4Header
->Id
;
320 IPDR
->Protocol
= IPv4Header
->Protocol
;
321 IPDR
->TimeoutCount
= 0;
322 InitializeListHead(&IPDR
->FragmentListHead
);
323 InitializeListHead(&IPDR
->HoleListHead
);
324 InsertTailList(&IPDR
->HoleListHead
, &Hole
->ListEntry
);
326 TcpipInitializeSpinLock(&IPDR
->Lock
);
328 TcpipAcquireSpinLock(&IPDR
->Lock
, &OldIrql
);
330 /* Update the reassembly list */
331 TcpipInterlockedInsertTailList(
334 &ReassemblyListLock
);
337 FragFirst
= (WN2H(IPv4Header
->FlagsFragOfs
) & IPv4_FRAGOFS_MASK
) << 3;
338 FragLast
= FragFirst
+ WN2H(IPv4Header
->TotalLength
);
339 MoreFragments
= (WN2H(IPv4Header
->FlagsFragOfs
) & IPv4_MF_MASK
) > 0;
341 CurrentEntry
= IPDR
->HoleListHead
.Flink
;
343 if (CurrentEntry
== &IPDR
->HoleListHead
)
346 Hole
= CONTAINING_RECORD(CurrentEntry
, IPDATAGRAM_HOLE
, ListEntry
);
348 TI_DbgPrint(DEBUG_IP
, ("Comparing Fragment (%d,%d) to Hole (%d,%d).\n",
349 FragFirst
, FragLast
, Hole
->First
, Hole
->Last
));
351 if ((FragFirst
> Hole
->Last
) || (FragLast
< Hole
->First
)) {
352 TI_DbgPrint(MID_TRACE
, ("No overlap.\n"));
353 /* The fragment does not overlap with the hole, try next
354 descriptor in the list */
356 CurrentEntry
= CurrentEntry
->Flink
;
360 /* The fragment overlap with the hole, unlink the descriptor */
361 RemoveEntryList(CurrentEntry
);
363 if (FragFirst
> Hole
->First
) {
364 NewHole
= CreateHoleDescriptor(Hole
->First
, FragFirst
- 1);
366 /* We don't have the resources to process this packet, discard it */
367 ExFreeToNPagedLookasideList(&IPHoleList
, Hole
);
368 Cleanup(&IPDR
->Lock
, OldIrql
, IPDR
);
372 /* Put the new descriptor in the list */
373 InsertTailList(&IPDR
->HoleListHead
, &NewHole
->ListEntry
);
376 if ((FragLast
< Hole
->Last
) && MoreFragments
) {
377 NewHole
= CreateHoleDescriptor(FragLast
+ 1, Hole
->Last
);
379 /* We don't have the resources to process this packet, discard it */
380 ExFreeToNPagedLookasideList(&IPHoleList
, Hole
);
381 Cleanup(&IPDR
->Lock
, OldIrql
, IPDR
);
385 /* Put the new hole descriptor in the list */
386 InsertTailList(&IPDR
->HoleListHead
, &NewHole
->ListEntry
);
389 ExFreeToNPagedLookasideList(&IPHoleList
, Hole
);
391 /* If this is the first fragment, save the IP header */
392 if (FragFirst
== 0) {
393 TI_DbgPrint(DEBUG_IP
, ("First fragment found. Header buffer is at (0x%X). "
394 "Header size is (%d).\n", &IPDR
->IPv4Header
, IPPacket
->HeaderSize
));
396 RtlCopyMemory(&IPDR
->IPv4Header
, IPPacket
->Header
, IPPacket
->HeaderSize
);
397 IPDR
->HeaderSize
= IPPacket
->HeaderSize
;
400 /* Create a buffer, copy the data into it and put it
401 in the fragment list */
403 Fragment
= ExAllocateFromNPagedLookasideList(&IPFragmentList
);
405 /* We don't have the resources to process this packet, discard it */
406 Cleanup(&IPDR
->Lock
, OldIrql
, IPDR
);
410 TI_DbgPrint(DEBUG_IP
, ("Fragment descriptor allocated at (0x%X).\n", Fragment
));
412 Fragment
->Size
= IPPacket
->TotalSize
- IPPacket
->HeaderSize
;
413 Fragment
->Data
= ExAllocatePoolWithTag(NonPagedPool
, Fragment
->Size
, FRAGMENT_DATA_TAG
);
414 if (!Fragment
->Data
) {
415 /* We don't have the resources to process this packet, discard it */
416 ExFreeToNPagedLookasideList(&IPFragmentList
, Fragment
);
417 Cleanup(&IPDR
->Lock
, OldIrql
, IPDR
);
421 /* Position here is an offset from the NdisPacket start, not the header */
422 TI_DbgPrint(DEBUG_IP
, ("Fragment data buffer allocated at (0x%X) Size (%d) Pos (%d).\n",
423 Fragment
->Data
, Fragment
->Size
, IPPacket
->Position
));
425 /* Copy datagram data into fragment buffer */
426 CopyPacketToBuffer(Fragment
->Data
,
427 IPPacket
->NdisPacket
,
428 IPPacket
->HeaderSize
,
430 Fragment
->Offset
= FragFirst
;
432 /* If this is the last fragment, compute and save the datagram data size */
434 IPDR
->DataSize
= FragFirst
+ Fragment
->Size
;
436 /* Put the fragment in the list */
437 InsertTailList(&IPDR
->FragmentListHead
, &Fragment
->ListEntry
);
441 TI_DbgPrint(DEBUG_IP
, ("Done searching for hole descriptor.\n"));
443 if (IsListEmpty(&IPDR
->HoleListHead
)) {
444 /* Hole list is empty which means a complete datagram can be assembled.
445 Assemble the datagram and pass it to an upper layer protocol */
447 TI_DbgPrint(DEBUG_IP
, ("Complete datagram received.\n"));
449 /* FIXME: Assumes IPv4 */
450 IPInitializePacket(&Datagram
, IP_ADDRESS_V4
);
452 Success
= ReassembleDatagram(&Datagram
, IPDR
);
455 TcpipReleaseSpinLock(&IPDR
->Lock
, OldIrql
);
460 /* Not enough free resources, discard the packet */
463 DISPLAY_IP_PACKET(&Datagram
);
465 /* Give the packet to the protocol dispatcher */
466 IPDispatchProtocol(IF
, &Datagram
);
468 IF
->Stats
.InBytes
+= Datagram
.TotalSize
;
470 /* We're done with this datagram */
471 ExFreePoolWithTag(Datagram
.Header
, PACKET_BUFFER_TAG
);
472 TI_DbgPrint(MAX_TRACE
, ("Freeing datagram at (0x%X).\n", Datagram
));
473 (*Datagram
.Free
)(&Datagram
);
475 TcpipReleaseSpinLock(&IPDR
->Lock
, OldIrql
);
479 VOID
IPFreeReassemblyList(
482 * FUNCTION: Frees all IP datagram reassembly structures in the list
486 PLIST_ENTRY CurrentEntry
;
487 PIPDATAGRAM_REASSEMBLY Current
;
489 TcpipAcquireSpinLock(&ReassemblyListLock
, &OldIrql
);
491 CurrentEntry
= ReassemblyListHead
.Flink
;
492 while (CurrentEntry
!= &ReassemblyListHead
) {
493 Current
= CONTAINING_RECORD(CurrentEntry
, IPDATAGRAM_REASSEMBLY
, ListEntry
);
494 /* Unlink it from the list */
495 RemoveEntryList(CurrentEntry
);
497 /* And free the descriptor */
500 CurrentEntry
= CurrentEntry
->Flink
;
503 TcpipReleaseSpinLock(&ReassemblyListLock
, OldIrql
);
507 VOID
IPDatagramReassemblyTimeout(
510 * FUNCTION: IP datagram reassembly timeout handler
512 * This routine is called by IPTimeout to free any resources used
513 * to hold IP fragments that have taken too long to reassemble
516 PLIST_ENTRY CurrentEntry
, NextEntry
;
517 PIPDATAGRAM_REASSEMBLY CurrentIPDR
;
519 TcpipAcquireSpinLockAtDpcLevel(&ReassemblyListLock
);
521 CurrentEntry
= ReassemblyListHead
.Flink
;
522 while (CurrentEntry
!= &ReassemblyListHead
)
524 NextEntry
= CurrentEntry
->Flink
;
525 CurrentIPDR
= CONTAINING_RECORD(CurrentEntry
, IPDATAGRAM_REASSEMBLY
, ListEntry
);
527 TcpipAcquireSpinLockAtDpcLevel(&CurrentIPDR
->Lock
);
529 if (++CurrentIPDR
->TimeoutCount
== MAX_TIMEOUT_COUNT
)
531 TcpipReleaseSpinLockFromDpcLevel(&CurrentIPDR
->Lock
);
532 RemoveEntryList(CurrentEntry
);
533 FreeIPDR(CurrentIPDR
);
537 ASSERT(CurrentIPDR
->TimeoutCount
< MAX_TIMEOUT_COUNT
);
538 TcpipReleaseSpinLockFromDpcLevel(&CurrentIPDR
->Lock
);
541 CurrentEntry
= NextEntry
;
544 TcpipReleaseSpinLockFromDpcLevel(&ReassemblyListLock
);
547 VOID
IPv4Receive(PIP_INTERFACE IF
, PIP_PACKET IPPacket
)
549 * FUNCTION: Receives an IPv4 datagram (or fragment)
551 * Context = Pointer to context information (IP_INTERFACE)
552 * IPPacket = Pointer to IP packet
555 TI_DbgPrint(DEBUG_IP
, ("Received IPv4 datagram.\n"));
557 IPPacket
->HeaderSize
= (((PIPv4_HEADER
)IPPacket
->Header
)->VerIHL
& 0x0F) << 2;
558 TI_DbgPrint(DEBUG_IP
, ("IPPacket->HeaderSize = %d\n", IPPacket
->HeaderSize
));
560 if (IPPacket
->HeaderSize
> IPv4_MAX_HEADER_SIZE
) {
563 ("Datagram received with incorrect header size (%d).\n",
564 IPPacket
->HeaderSize
));
569 /* Checksum IPv4 header */
570 if (!IPv4CorrectChecksum(IPPacket
->Header
, IPPacket
->HeaderSize
)) {
573 ("Datagram received with bad checksum. Checksum field (0x%X)\n",
574 WN2H(((PIPv4_HEADER
)IPPacket
->Header
)->Checksum
)));
579 IPPacket
->TotalSize
= WN2H(((PIPv4_HEADER
)IPPacket
->Header
)->TotalLength
);
581 AddrInitIPv4(&IPPacket
->SrcAddr
, ((PIPv4_HEADER
)IPPacket
->Header
)->SrcAddr
);
582 AddrInitIPv4(&IPPacket
->DstAddr
, ((PIPv4_HEADER
)IPPacket
->Header
)->DstAddr
);
584 IPPacket
->Position
+= IPPacket
->HeaderSize
;
585 IPPacket
->Data
= (PVOID
)((ULONG_PTR
)IPPacket
->Header
+ IPPacket
->HeaderSize
);
587 TI_DbgPrint(MID_TRACE
,("IPPacket->Position = %d\n",
588 IPPacket
->Position
));
590 //OskitDumpBuffer(IPPacket->Header, IPPacket->TotalSize);
592 /* FIXME: Possibly forward packets with multicast addresses */
594 /* FIXME: Should we allow packets to be received on the wrong interface? */
595 /* XXX Find out if this packet is destined for us */
596 ProcessFragment(IF
, IPPacket
);
599 /* This packet is not destined for us. If we are a router,
600 try to find a route and forward the packet */
602 /* FIXME: Check if acting as a router */
605 PROUTE_CACHE_NODE RCN
;
607 /* FIXME: Possibly fragment datagram */
608 /* Forward the packet */
609 if(!RouteGetRouteToDestination( &IPPacket
->DstAddr
, NULL
, &RCN
))
610 IPSendDatagram(IPPacket
, RCN
, ReflectPacketComplete
, IPPacket
);
612 TI_DbgPrint(MIN_TRACE
, ("No route to destination (0x%X).\n",
613 IPPacket
->DstAddr
.Address
.IPv4Address
));
615 /* FIXME: Send ICMP error code */
622 VOID
IPReceive( PIP_INTERFACE IF
, PIP_PACKET IPPacket
)
624 * FUNCTION: Receives an IP datagram (or fragment)
627 * IPPacket = Pointer to IP packet
632 /* Check that IP header has a supported version */
633 Version
= (((PIPv4_HEADER
)IPPacket
->Header
)->VerIHL
>> 4);
637 IPPacket
->Type
= IP_ADDRESS_V4
;
638 IPv4Receive(IF
, IPPacket
);
641 IPPacket
->Type
= IP_ADDRESS_V6
;
642 TI_DbgPrint(MAX_TRACE
, ("Datagram of type IPv6 discarded.\n"));
645 TI_DbgPrint(MIN_TRACE
, ("Datagram has an unsupported IP version %d.\n", Version
));
649 IPPacket
->Free(IPPacket
);