-/*\r
- * COPYRIGHT: See COPYING in the top level directory\r
- * PROJECT: ReactOS TCP/IP protocol driver\r
- * FILE: network/ip.c\r
- * PURPOSE: Internet Protocol module\r
- * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)\r
- * REVISIONS:\r
- * CSH 01/08-2000 Created\r
- */\r
-#include <tcpip.h>\r
-#include <ip.h>\r
-#include <loopback.h>\r
-#include <neighbor.h>\r
-#include <receive.h>\r
-#include <address.h>\r
-#include <route.h>\r
-#include <icmp.h>\r
-#include <pool.h>\r
-\r
-\r
-KTIMER IPTimer;\r
-KDPC IPTimeoutDpc;\r
-LIST_ENTRY InterfaceListHead;\r
-KSPIN_LOCK InterfaceListLock;\r
-LIST_ENTRY NetTableListHead;\r
-KSPIN_LOCK NetTableListLock;\r
-LIST_ENTRY PrefixListHead;\r
-KSPIN_LOCK PrefixListLock;\r
-UINT MaxLLHeaderSize; /* Largest maximum header size */\r
-UINT MinLLFrameSize; /* Largest minimum frame size */\r
-BOOLEAN IPInitialized = FALSE;\r
-\r
-IP_PROTOCOL_HANDLER ProtocolTable[IP_PROTOCOL_TABLE_SIZE];\r
-\r
-\r
-PADDRESS_ENTRY CreateADE(\r
- PIP_INTERFACE IF,\r
- PIP_ADDRESS Address,\r
- UCHAR Type,\r
- PNET_TABLE_ENTRY NTE)\r
-/*\r
- * FUNCTION: Creates an address entry and binds it to an interface\r
- * ARGUMENTS:\r
- * IF = Pointer to interface\r
- * Address = Pointer to referenced interface address\r
- * Type = Type of address (ADE_*)\r
- * NTE = Pointer to net table entry\r
- * RETURNS:\r
- * Pointer to ADE, NULL if there was not enough free resources\r
- * NOTES:\r
- * The interface lock must be held when called. The address entry\r
- * retains a reference to the provided address and NTE. The caller\r
- * is responsible for referencing the these before calling.\r
- * As long as you have referenced an ADE you can safely use the\r
- * address and NTE as the ADE references both\r
- */\r
-{\r
- PADDRESS_ENTRY ADE;\r
-\r
- TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X) Address (0x%X) Type (0x%X) NTE (0x%X).\n",\r
- IF, Address, Type, NTE));\r
-\r
- /* Allocate space for an ADE and set it up */\r
- ADE = PoolAllocateBuffer(sizeof(ADDRESS_ENTRY));\r
- if (!ADE) {\r
- TI_DbgPrint(MIN_TRACE, ("Insufficient resources.\n"));\r
- return NULL;\r
- }\r
-\r
- ADE->RefCount = 1;\r
- ADE->NTE = NTE;\r
- ADE->Type = Type;\r
- ADE->Address = Address;\r
-\r
- /* Add ADE to the list on the interface */\r
- InsertTailList(&IF->ADEListHead, &ADE->ListEntry);\r
-\r
- return ADE;\r
-}\r
-\r
-\r
-VOID DestroyADE(\r
- PIP_INTERFACE IF,\r
- PADDRESS_ENTRY ADE)\r
-/*\r
- * FUNCTION: Destroys an address entry\r
- * ARGUMENTS:\r
- * IF = Pointer to interface\r
- * ADE = Pointer to address entry\r
- * NOTES:\r
- * The interface lock must be held when called\r
- */\r
-{\r
- TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X) ADE (0x%X).\n", IF, ADE));\r
-\r
- /* Unlink the address entry from the list */\r
- RemoveEntryList(&ADE->ListEntry);\r
-\r
- /* Dereference the address */\r
- DereferenceObject(ADE->Address);\r
-\r
- /* Dereference the NTE */\r
- DereferenceObject(ADE->NTE);\r
-\r
-#ifdef DBG\r
- ADE->RefCount--;\r
-\r
- if (ADE->RefCount != 0) {\r
- TI_DbgPrint(MIN_TRACE, ("Address entry at (0x%X) has (%d) references (should be 0).\n", ADE, ADE->RefCount));\r
- }\r
-#endif\r
-\r
- /* And free the ADE */\r
- PoolFreeBuffer(ADE);\r
- TI_DbgPrint(MIN_TRACE, ("Check.\n"));\r
-}\r
-\r
-\r
-VOID DestroyADEs(\r
- PIP_INTERFACE IF)\r
-/*\r
- * FUNCTION: Destroys all address entries on an interface\r
- * ARGUMENTS:\r
- * IF = Pointer to interface\r
- * NOTES:\r
- * The interface lock must be held when called\r
- */\r
-{\r
- PLIST_ENTRY CurrentEntry;\r
- PLIST_ENTRY NextEntry;\r
- PADDRESS_ENTRY Current;\r
-\r
- TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X).\n", IF));\r
-\r
- /* Search the list and remove every ADE we find */\r
- CurrentEntry = IF->ADEListHead.Flink;\r
- while (CurrentEntry != &IF->ADEListHead) {\r
- NextEntry = CurrentEntry->Flink;\r
- Current = CONTAINING_RECORD(CurrentEntry, ADDRESS_ENTRY, ListEntry);\r
- /* Destroy the ADE */\r
- DestroyADE(IF, Current);\r
- CurrentEntry = NextEntry;\r
- }\r
-}\r
-\r
-\r
-PPREFIX_LIST_ENTRY CreatePLE(\r
- PIP_INTERFACE IF,\r
- PIP_ADDRESS Prefix,\r
- UINT Length)\r
-/*\r
- * FUNCTION: Creates a prefix list entry and binds it to an interface\r
- * ARGUMENTS:\r
- * IF = Pointer to interface\r
- * Prefix = Pointer to prefix\r
- * Length = Length of prefix\r
- * RETURNS:\r
- * Pointer to PLE, NULL if there was not enough free resources\r
- * NOTES:\r
- * The prefix list entry retains a reference to the interface and\r
- * the provided address. The caller is responsible for providing\r
- * these references\r
- */\r
-{\r
- PPREFIX_LIST_ENTRY PLE;\r
-\r
- TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X) Prefix (0x%X) Length (%d).\n", IF, Prefix, Length));\r
-\r
- /* Allocate space for an PLE and set it up */\r
- PLE = PoolAllocateBuffer(sizeof(PREFIX_LIST_ENTRY));\r
- if (!PLE) {\r
- TI_DbgPrint(MIN_TRACE, ("Insufficient resources.\n"));\r
- return NULL;\r
- }\r
-\r
- PLE->RefCount = 1;\r
- PLE->Interface = IF;\r
- PLE->Prefix = Prefix;\r
- PLE->PrefixLength = Length;\r
-\r
- /* Add PLE to the global prefix list */\r
- ExInterlockedInsertTailList(&PrefixListHead, &PLE->ListEntry, &PrefixListLock);\r
-\r
- return PLE;\r
-}\r
-\r
-\r
-VOID DestroyPLE(\r
- PPREFIX_LIST_ENTRY PLE)\r
-/*\r
- * FUNCTION: Destroys an prefix list entry\r
- * ARGUMENTS:\r
- * PLE = Pointer to prefix list entry\r
- * NOTES:\r
- * The prefix list lock must be held when called\r
- */\r
-{\r
- TI_DbgPrint(DEBUG_IP, ("Called. PLE (0x%X).\n", PLE));\r
-\r
- /* Unlink the prefix list entry from the list */\r
- RemoveEntryList(&PLE->ListEntry);\r
-\r
- /* Dereference the address */\r
- DereferenceObject(PLE->Prefix);\r
-\r
- /* Dereference the interface */\r
- DereferenceObject(PLE->Interface);\r
-\r
-#ifdef DBG\r
- PLE->RefCount--;\r
-\r
- if (PLE->RefCount != 0) {\r
- TI_DbgPrint(MIN_TRACE, ("Prefix list entry at (0x%X) has (%d) references (should be 0).\n", PLE, PLE->RefCount));\r
- }\r
-#endif\r
-\r
- /* And free the PLE */\r
- PoolFreeBuffer(PLE);\r
-}\r
-\r
-\r
-VOID DestroyPLEs(\r
- VOID)\r
-/*\r
- * FUNCTION: Destroys all prefix list entries\r
- */\r
-{\r
- KIRQL OldIrql;\r
- PLIST_ENTRY CurrentEntry;\r
- PLIST_ENTRY NextEntry;\r
- PPREFIX_LIST_ENTRY Current;\r
-\r
- TI_DbgPrint(DEBUG_IP, ("Called.\n"));\r
-\r
- KeAcquireSpinLock(&PrefixListLock, &OldIrql);\r
-\r
- /* Search the list and remove every PLE we find */\r
- CurrentEntry = PrefixListHead.Flink;\r
- while (CurrentEntry != &PrefixListHead) {\r
- NextEntry = CurrentEntry->Flink;\r
- Current = CONTAINING_RECORD(CurrentEntry, PREFIX_LIST_ENTRY, ListEntry);\r
- /* Destroy the PLE */\r
- DestroyPLE(Current);\r
- CurrentEntry = NextEntry;\r
- }\r
- KeReleaseSpinLock(&PrefixListLock, OldIrql);\r
-}\r
-\r
-\r
-PNET_TABLE_ENTRY IPCreateNTE(\r
- PIP_INTERFACE IF,\r
- PIP_ADDRESS Address,\r
- UINT PrefixLength)\r
-/*\r
- * FUNCTION: Creates a net table entry and binds it to an interface\r
- * ARGUMENTS:\r
- * IF = Pointer to interface\r
- * Address = Pointer to interface address\r
- * PrefixLength = Length of prefix\r
- * RETURNS:\r
- * Pointer to NTE, NULL if there was not enough free resources\r
- * NOTES:\r
- * The interface lock must be held when called.\r
- * The net table entry retains a reference to the interface and\r
- * the provided address. The caller is responsible for providing\r
- * these references\r
- */\r
-{\r
- PNET_TABLE_ENTRY NTE;\r
- PADDRESS_ENTRY ADE;\r
-\r
- TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X) Address (0x%X) PrefixLength (%d).\n", IF, Address, PrefixLength));\r
-\r
- /* Allocate room for an NTE */\r
- NTE = PoolAllocateBuffer(sizeof(NET_TABLE_ENTRY));\r
- if (!NTE) {\r
- TI_DbgPrint(MIN_TRACE, ("Insufficient resources.\n"));\r
- return NULL;\r
- }\r
-\r
- NTE->Interface = IF;\r
-\r
- /* One reference is for beeing alive and one reference is for the ADE */\r
- NTE->RefCount = 2;\r
-\r
- NTE->Address = Address;\r
- /* One reference is for NTE, one reference is given to the\r
- address entry, and one reference is given to the prefix\r
- list entry */\r
- ReferenceObject(Address);\r
- ReferenceObject(Address);\r
- ReferenceObject(Address);\r
-\r
- /* Create an address entry and add it to the list */\r
- ADE = CreateADE(IF, NTE->Address, ADE_UNICAST, NTE);\r
- if (!ADE) {\r
- TI_DbgPrint(MIN_TRACE, ("Insufficient resources.\n"));\r
- PoolFreeBuffer(NTE);\r
- return NULL;\r
- }\r
-\r
- /* Create a prefix list entry for unicast address */\r
- NTE->PLE = CreatePLE(IF, NTE->Address, PrefixLength);\r
- if (!NTE->PLE) {\r
- DestroyADE(IF, ADE);\r
- PoolFreeBuffer(NTE);\r
- return NULL;\r
- }\r
-\r
- /* Reference the interface for the prefix list entry */\r
- ReferenceObject(IF);\r
-\r
- /* Add NTE to the list on the interface */\r
- InsertTailList(&IF->NTEListHead, &NTE->IFListEntry);\r
-\r
- /* Add NTE to the global net table list */\r
- ExInterlockedInsertTailList(&NetTableListHead, &NTE->NTListEntry, &NetTableListLock);\r
-\r
- return NTE;\r
-}\r
-\r
-\r
-VOID DestroyNTE(\r
- PIP_INTERFACE IF,\r
- PNET_TABLE_ENTRY NTE)\r
-/*\r
- * FUNCTION: Destroys a net table entry\r
- * ARGUMENTS:\r
- * IF = Pointer to interface\r
- * NTE = Pointer to net table entry\r
- * NOTES:\r
- * The net table list lock must be held when called\r
- * The interface lock must be held when called\r
- */\r
-{\r
- KIRQL OldIrql;\r
-\r
- TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X) NTE (0x%X).\n", IF, NTE));\r
-\r
- /* Invalidate the prefix list entry for this NTE */\r
- KeAcquireSpinLock(&PrefixListLock, &OldIrql);\r
- DestroyPLE(NTE->PLE);\r
- KeReleaseSpinLock(&PrefixListLock, OldIrql);\r
-\r
- /* Remove NTE from the interface list */\r
- RemoveEntryList(&NTE->IFListEntry);\r
- /* Remove NTE from the net table list */\r
- RemoveEntryList(&NTE->NTListEntry);\r
- /* Dereference the objects that are referenced */\r
- DereferenceObject(NTE->Address);\r
- DereferenceObject(NTE->Interface);\r
-#ifdef DBG\r
- NTE->RefCount--;\r
-\r
- if (NTE->RefCount != 0) {\r
- TI_DbgPrint(MIN_TRACE, ("Net table entry at (0x%X) has (%d) references (should be 0).\n", NTE, NTE->RefCount));\r
- }\r
-#endif\r
- /* And free the NTE */\r
- PoolFreeBuffer(NTE);\r
-}\r
-\r
-\r
-VOID DestroyNTEs(\r
- PIP_INTERFACE IF)\r
-/*\r
- * FUNCTION: Destroys all net table entries on an interface\r
- * ARGUMENTS:\r
- * IF = Pointer to interface\r
- * NOTES:\r
- * The net table list lock must be held when called\r
- * The interface lock may be held when called\r
- */\r
-{\r
- PLIST_ENTRY CurrentEntry;\r
- PLIST_ENTRY NextEntry;\r
- PNET_TABLE_ENTRY Current;\r
-\r
- TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X).\n", IF));\r
-\r
- /* Search the list and remove every NTE we find */\r
- CurrentEntry = IF->NTEListHead.Flink;\r
- while (CurrentEntry != &IF->NTEListHead) {\r
- NextEntry = CurrentEntry->Flink;\r
- Current = CONTAINING_RECORD(CurrentEntry, NET_TABLE_ENTRY, IFListEntry);\r
- /* Destroy the NTE */\r
- DestroyNTE(IF, Current);\r
- CurrentEntry = NextEntry;\r
- }\r
-}\r
-\r
-\r
-PNET_TABLE_ENTRY IPLocateNTEOnInterface(\r
- PIP_INTERFACE IF,\r
- PIP_ADDRESS Address,\r
- PUINT AddressType)\r
-/*\r
- * FUNCTION: Locates an NTE on an interface\r
- * ARGUMENTS:\r
- * IF = Pointer to interface\r
- * Address = Pointer to IP address\r
- * AddressType = Address of type of IP address\r
- * NOTES:\r
- * If found, the NTE is referenced for the caller. The caller is\r
- * responsible for dereferencing after use\r
- * RETURNS:\r
- * Pointer to net table entry, NULL if none was found\r
- */\r
-{\r
- KIRQL OldIrql;\r
- PLIST_ENTRY CurrentEntry;\r
- PADDRESS_ENTRY Current;\r
-\r
- TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X) Address (0x%X) AddressType (0x%X).\n",\r
- IF, Address, AddressType));\r
-\r
- KeAcquireSpinLock(&IF->Lock, &OldIrql);\r
-\r
- /* Search the list and return the NTE if found */\r
- CurrentEntry = IF->ADEListHead.Flink;\r
- while (CurrentEntry != &IF->ADEListHead) {\r
- Current = CONTAINING_RECORD(CurrentEntry, ADDRESS_ENTRY, ListEntry);\r
- if (AddrIsEqual(Address, Current->Address)) {\r
- ReferenceObject(Current->NTE);\r
- *AddressType = Current->Type;\r
- KeReleaseSpinLock(&IF->Lock, OldIrql);\r
- return Current->NTE;\r
- }\r
- CurrentEntry = CurrentEntry->Flink;\r
- }\r
-\r
- KeReleaseSpinLock(&IF->Lock, OldIrql);\r
-\r
- return NULL;\r
-}\r
-\r
-\r
-PNET_TABLE_ENTRY IPLocateNTE(\r
- PIP_ADDRESS Address,\r
- PUINT AddressType)\r
-/*\r
- * FUNCTION: Locates an NTE for the network Address is on \r
- * ARGUMENTS:\r
- * Address = Pointer to an address to find associated NTE of\r
- * AddressType = Address of address type\r
- * NOTES:\r
- * If found the NTE is referenced for the caller. The caller is\r
- * responsible for dereferencing after use\r
- * RETURNS:\r
- * Pointer to NTE if the address was found, NULL if not.\r
- */\r
-{\r
- KIRQL OldIrql;\r
- PLIST_ENTRY CurrentEntry;\r
- PNET_TABLE_ENTRY Current;\r
- PNET_TABLE_ENTRY NTE;\r
-\r
- TI_DbgPrint(DEBUG_IP, ("Called. Address (0x%X) AddressType (0x%X).\n",\r
- Address, AddressType));\r
- \r
- KeAcquireSpinLock(&NetTableListLock, &OldIrql);\r
-\r
- /* Search the list and return the NTE if found */\r
- CurrentEntry = NetTableListHead.Flink;\r
- while (CurrentEntry != &NetTableListHead) {\r
- Current = CONTAINING_RECORD(CurrentEntry, NET_TABLE_ENTRY, NTListEntry);\r
- NTE = IPLocateNTEOnInterface(Current->Interface, Address, AddressType);\r
- if (NTE) {\r
- ReferenceObject(NTE);\r
- KeReleaseSpinLock(&NetTableListLock, OldIrql);\r
- return NTE;\r
- }\r
- CurrentEntry = CurrentEntry->Flink;\r
- }\r
-\r
- KeReleaseSpinLock(&NetTableListLock, OldIrql);\r
-\r
- return NULL;\r
-}\r
-\r
-\r
-PADDRESS_ENTRY IPLocateADE(\r
- PIP_ADDRESS Address,\r
- UINT AddressType)\r
-/*\r
- * FUNCTION: Locates an ADE for the address\r
- * ARGUMENTS:\r
- * Address = Pointer to an address to find associated ADE of\r
- * AddressType = Type of address\r
- * RETURNS:\r
- * Pointer to ADE if the address was found, NULL if not.\r
- * NOTES:\r
- * If found the ADE is referenced for the caller. The caller is\r
- * responsible for dereferencing after use\r
- */\r
-{\r
- KIRQL OldIrql;\r
- PLIST_ENTRY CurrentIFEntry;\r
- PLIST_ENTRY CurrentADEEntry;\r
- PIP_INTERFACE CurrentIF;\r
- PADDRESS_ENTRY CurrentADE;\r
-\r
- TI_DbgPrint(DEBUG_IP, ("Called. Address (0x%X) AddressType (0x%X).\n",\r
- Address, AddressType));\r
-\r
- KeAcquireSpinLock(&InterfaceListLock, &OldIrql);\r
-\r
- /* Search the interface list */\r
- CurrentIFEntry = InterfaceListHead.Flink;\r
- while (CurrentIFEntry != &InterfaceListHead) {\r
- CurrentIF = CONTAINING_RECORD(CurrentIFEntry, IP_INTERFACE, ListEntry);\r
-\r
- /* Search the address entry list and return the ADE if found */\r
- CurrentADEEntry = CurrentIF->ADEListHead.Flink;\r
- while (CurrentADEEntry != &CurrentIF->ADEListHead) {\r
- CurrentADE = CONTAINING_RECORD(CurrentADEEntry, ADDRESS_ENTRY, ListEntry);\r
- if ((AddrIsEqual(Address, CurrentADE->Address)) && \r
- (CurrentADE->Type == AddressType)) {\r
- ReferenceObject(CurrentADE);\r
- KeReleaseSpinLock(&InterfaceListLock, OldIrql);\r
- return CurrentADE;\r
- }\r
- CurrentADEEntry = CurrentADEEntry->Flink;\r
- }\r
- CurrentIFEntry = CurrentIFEntry->Flink;\r
- }\r
-\r
- KeReleaseSpinLock(&InterfaceListLock, OldIrql);\r
-\r
- return NULL;\r
-}\r
-\r
-\r
-PADDRESS_ENTRY IPGetDefaultADE(\r
- UINT AddressType)\r
-/*\r
- * FUNCTION: Returns a default address entry\r
- * ARGUMENTS:\r
- * AddressType = Type of address\r
- * RETURNS:\r
- * Pointer to ADE if found, NULL if not.\r
- * NOTES:\r
- * Loopback interface is only considered if it is the only interface.\r
- * If found, the address entry is referenced\r
- */\r
-{\r
- KIRQL OldIrql;\r
- PLIST_ENTRY CurrentIFEntry;\r
- PLIST_ENTRY CurrentADEEntry;\r
- PIP_INTERFACE CurrentIF;\r
- PADDRESS_ENTRY CurrentADE;\r
-#if 0\r
- BOOLEAN LoopbackIsRegistered = FALSE;\r
-#endif\r
- TI_DbgPrint(DEBUG_IP, ("Called. AddressType (0x%X).\n", AddressType));\r
-\r
- KeAcquireSpinLock(&InterfaceListLock, &OldIrql);\r
-\r
- /* Search the interface list */\r
- CurrentIFEntry = InterfaceListHead.Flink;\r
- while (CurrentIFEntry != &InterfaceListHead) {\r
- CurrentIF = CONTAINING_RECORD(CurrentIFEntry, IP_INTERFACE, ListEntry);\r
-#if 0\r
- if (CurrentIF != Loopback) {\r
-#endif\r
- /* Search the address entry list and return the first appropriate ADE found */\r
- CurrentADEEntry = CurrentIF->ADEListHead.Flink;\r
- while (CurrentADEEntry != &CurrentIF->ADEListHead) {\r
- CurrentADE = CONTAINING_RECORD(CurrentADEEntry, ADDRESS_ENTRY, ListEntry);\r
- if (CurrentADE->Type == AddressType)\r
- ReferenceObject(CurrentADE);\r
- KeReleaseSpinLock(&InterfaceListLock, OldIrql);\r
- return CurrentADE;\r
- }\r
- CurrentADEEntry = CurrentADEEntry->Flink;\r
-#if 0\r
- } else\r
- LoopbackIsRegistered = TRUE;\r
-#endif\r
- CurrentIFEntry = CurrentIFEntry->Flink;\r
- }\r
-#if 0\r
- /* No address was found. Use loopback interface if available */\r
- if (LoopbackIsRegistered) {\r
- CurrentADEEntry = Loopback->ADEListHead.Flink;\r
- while (CurrentADEEntry != &Loopback->ADEListHead) {\r
- CurrentADE = CONTAINING_RECORD(CurrentADEEntry, ADDRESS_ENTRY, ListEntry);\r
- if (CurrentADE->Type == AddressType) {\r
- ReferenceObject(CurrentADE);\r
- KeReleaseSpinLock(&InterfaceListLock, OldIrql);\r
- return CurrentADE;\r
- }\r
- CurrentADEEntry = CurrentADEEntry->Flink;\r
- }\r
- }\r
-#endif\r
- KeReleaseSpinLock(&InterfaceListLock, OldIrql);\r
-\r
- return NULL;\r
-}\r
-\r
-\r
-VOID IPTimeout(\r
- PKDPC Dpc,\r
- PVOID DeferredContext,\r
- PVOID SystemArgument1,\r
- PVOID SystemArgument2)\r
-/*\r
- * FUNCTION: Timeout DPC\r
- * ARGUMENTS:\r
- * Dpc = Pointer to our DPC object\r
- * DeferredContext = Pointer to context information (unused)\r
- * SystemArgument1 = Unused\r
- * SystemArgument2 = Unused\r
- * NOTES:\r
- * This routine is dispatched once in a while to do maintainance jobs\r
- */\r
-{\r
- /* Check if datagram fragments have taken too long to assemble */\r
- IPDatagramReassemblyTimeout();\r
-\r
- /* Clean possible outdated cached neighbor addresses */\r
- NBTimeout();\r
-}\r
-\r
-\r
-VOID IPDispatchProtocol(\r
- PNET_TABLE_ENTRY NTE,\r
- PIP_PACKET IPPacket)\r
-/*\r
- * FUNCTION: IP protocol dispatcher\r
- * ARGUMENTS:\r
- * NTE = Pointer to net table entry which the packet was received on\r
- * IPPacket = Pointer to an IP packet that was received\r
- * NOTES:\r
- * This routine examines the IP header and passes the packet on to the\r
- * right upper level protocol receive handler\r
- */\r
-{\r
- UINT Protocol;\r
-\r
- switch (IPPacket->Type) {\r
- case IP_ADDRESS_V4:\r
- Protocol = ((PIPv4_HEADER)(IPPacket->Header))->Protocol;\r
- break;\r
- case IP_ADDRESS_V6:\r
- /* FIXME: IPv6 adresses not supported */\r
- TI_DbgPrint(MIN_TRACE, ("IPv6 datagram discarded.\n"));\r
- return;\r
- default:\r
- Protocol = 0;\r
- }\r
-\r
- /* Call the appropriate protocol handler */\r
- (*ProtocolTable[Protocol])(NTE, IPPacket);\r
-}\r
-\r
-\r
-PIP_INTERFACE IPCreateInterface(\r
- PLLIP_BIND_INFO BindInfo)\r
-/*\r
- * FUNCTION: Creates an IP interface\r
- * ARGUMENTS:\r
- * BindInfo = Pointer to link layer to IP binding information\r
- * RETURNS:\r
- * Pointer to IP_INTERFACE structure, NULL if there was\r
- * not enough free resources\r
- */\r
-{\r
- PIP_INTERFACE IF;\r
-\r
- TI_DbgPrint(DEBUG_IP, ("Called. BindInfo (0x%X).\n", BindInfo));\r
-\r
- IF = PoolAllocateBuffer(sizeof(IP_INTERFACE));\r
- if (!IF) {\r
- TI_DbgPrint(MIN_TRACE, ("Insufficient resources.\n"));\r
- return NULL;\r
- }\r
-\r
- IF->RefCount = 1;\r
- IF->Context = BindInfo->Context;\r
- IF->HeaderSize = BindInfo->HeaderSize;\r
- if (IF->HeaderSize > MaxLLHeaderSize)\r
- MaxLLHeaderSize = IF->HeaderSize;\r
-\r
- IF->MinFrameSize = BindInfo->MinFrameSize;\r
- if (IF->MinFrameSize > MinLLFrameSize)\r
- MinLLFrameSize = IF->MinFrameSize;\r
-\r
- IF->MTU = BindInfo->MTU;\r
- IF->Address = BindInfo->Address;\r
- IF->AddressLength = BindInfo->AddressLength;\r
- IF->Transmit = BindInfo->Transmit;\r
-\r
- InitializeListHead(&IF->ADEListHead);\r
- InitializeListHead(&IF->NTEListHead);\r
-\r
- KeInitializeSpinLock(&IF->Lock);\r
-\r
- return IF;\r
-}\r
-\r
-\r
-VOID IPDestroyInterface(\r
- PIP_INTERFACE IF)\r
-/*\r
- * FUNCTION: Destroys an IP interface\r
- * ARGUMENTS:\r
- * IF = Pointer to interface to destroy\r
- */\r
-{\r
- KIRQL OldIrql1;\r
- KIRQL OldIrql2;\r
-\r
- TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X).\n", IF));\r
-\r
- KeAcquireSpinLock(&NetTableListLock, &OldIrql1);\r
- KeAcquireSpinLock(&IF->Lock, &OldIrql2);\r
- DestroyADEs(IF);\r
- DestroyNTEs(IF);\r
- KeReleaseSpinLock(&IF->Lock, OldIrql2);\r
- KeReleaseSpinLock(&NetTableListLock, OldIrql1);\r
-\r
-#ifdef DBG\r
- IF->RefCount--;\r
-\r
- if (IF->RefCount != 0) {\r
- TI_DbgPrint(MIN_TRACE, ("Interface at (0x%X) has (%d) references (should be 0).\n", IF, IF->RefCount));\r
- }\r
-#endif\r
- PoolFreeBuffer(IF);\r
-}\r
-\r
-\r
-BOOLEAN IPRegisterInterface(\r
- PIP_INTERFACE IF)\r
-/*\r
- * FUNCTION: Registers an IP interface with IP layer\r
- * ARGUMENTS:\r
- * IF = Pointer to interface to register\r
- * RETURNS;\r
- * TRUE if interface was successfully registered, FALSE if not\r
- */\r
-{\r
- KIRQL OldIrql;\r
- PLIST_ENTRY CurrentEntry;\r
- PNET_TABLE_ENTRY Current;\r
- PROUTE_CACHE_NODE RCN;\r
- PNEIGHBOR_CACHE_ENTRY NCE;\r
-\r
- TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X).\n", IF));\r
-\r
- KeAcquireSpinLock(&IF->Lock, &OldIrql);\r
-\r
- /* Add routes to all NTEs on this interface */\r
- CurrentEntry = IF->NTEListHead.Flink;\r
- while (CurrentEntry != &IF->NTEListHead) {\r
- Current = CONTAINING_RECORD(CurrentEntry, NET_TABLE_ENTRY, IFListEntry);\r
-\r
- /* Add a permanent neighbor for this NTE */\r
- ReferenceObject(Current->Address);\r
- NCE = NBAddNeighbor(IF, Current->Address, IF->Address,\r
- IF->AddressLength, NUD_PERMANENT);\r
- if (!NCE) {\r
- TI_DbgPrint(MIN_TRACE, ("Could not create NCE.\n"));\r
- DereferenceObject(Current->Address);\r
- KeReleaseSpinLock(&IF->Lock, OldIrql);\r
- return FALSE;\r
- }\r
- RCN = RouteAddRouteToDestination(Current->Address, Current, IF, NCE);\r
- if (!RCN) {\r
- TI_DbgPrint(MIN_TRACE, ("Could not create RCN.\n"));\r
- DereferenceObject(Current->Address);\r
- KeReleaseSpinLock(&IF->Lock, OldIrql);\r
- return FALSE;\r
- }\r
- /* Don't need this any more since the route cache references the NCE */\r
- DereferenceObject(NCE);\r
-\r
- CurrentEntry = CurrentEntry->Flink;\r
- }\r
-\r
- /* Add interface to the global interface list */\r
- ExInterlockedInsertTailList(&InterfaceListHead, &IF->ListEntry, &InterfaceListLock);\r
-\r
- KeReleaseSpinLock(&IF->Lock, OldIrql);\r
-\r
- return TRUE;\r
-}\r
-\r
-\r
-VOID IPUnregisterInterface(\r
- PIP_INTERFACE IF)\r
-/*\r
- * FUNCTION: Unregisters an IP interface with IP layer\r
- * ARGUMENTS:\r
- * IF = Pointer to interface to unregister\r
- */\r
-{\r
- KIRQL OldIrql1;\r
- KIRQL OldIrql2;\r
- KIRQL OldIrql3;\r
- PLIST_ENTRY CurrentEntry;\r
- PNET_TABLE_ENTRY Current;\r
- PNEIGHBOR_CACHE_ENTRY NCE;\r
-\r
- TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X).\n", IF));\r
-\r
- KeAcquireSpinLock(&NetTableListLock, &OldIrql1);\r
- KeAcquireSpinLock(&IF->Lock, &OldIrql2);\r
-\r
- /* Remove routes to all NTEs on this interface */\r
- CurrentEntry = IF->NTEListHead.Flink;\r
- while (CurrentEntry != &IF->NTEListHead) {\r
- Current = CONTAINING_RECORD(CurrentEntry, NET_TABLE_ENTRY, IFListEntry);\r
-\r
- /* Remove NTE from global net table list */\r
- RemoveEntryList(&Current->NTListEntry);\r
-\r
- /* Remove all references from route cache to NTE */\r
- RouteInvalidateNTE(Current);\r
-\r
- /* Remove permanent NCE, but first we have to find it */\r
- NCE = NBLocateNeighbor(Current->Address);\r
- if (NCE) {\r
- DereferenceObject(NCE);\r
- NBRemoveNeighbor(NCE);\r
- }\r
-\r
- CurrentEntry = CurrentEntry->Flink;\r
- }\r
-\r
- KeAcquireSpinLock(&InterfaceListLock, &OldIrql3);\r
- /* Ouch...three spinlocks acquired! Fortunately\r
- we don't unregister interfaces very often */\r
- RemoveEntryList(&IF->ListEntry);\r
- KeReleaseSpinLock(&InterfaceListLock, OldIrql3);\r
-\r
- KeReleaseSpinLock(&IF->Lock, OldIrql2);\r
- KeReleaseSpinLock(&NetTableListLock, OldIrql1);\r
-}\r
-\r
-\r
-VOID IPRegisterProtocol(\r
- UINT ProtocolNumber,\r
- IP_PROTOCOL_HANDLER Handler)\r
-/*\r
- * FUNCTION: Registers a handler for an IP protocol number\r
- * ARGUMENTS:\r
- * ProtocolNumber = Internet Protocol number for which to register handler\r
- * Handler = Pointer to handler to be called when a packet is received\r
- * NOTES:\r
- * To unregister a protocol handler, call this function with Handler = NULL\r
- */\r
-{\r
-#ifdef DBG\r
- if (ProtocolNumber >= IP_PROTOCOL_TABLE_SIZE)\r
- TI_DbgPrint(MIN_TRACE, ("Protocol number is out of range (%d).\n", ProtocolNumber));\r
-#endif\r
-\r
- ProtocolTable[ProtocolNumber] = Handler;\r
-}\r
-\r
-\r
-VOID DefaultProtocolHandler(\r
- PNET_TABLE_ENTRY NTE,\r
- PIP_PACKET IPPacket)\r
-/*\r
- * FUNCTION: Default handler for Internet protocols\r
- * ARGUMENTS:\r
- * NTE = Pointer to net table entry which the packet was received on\r
- * IPPacket = Pointer to an IP packet that was received\r
- */\r
-{\r
- TI_DbgPrint(MID_TRACE, ("Packet of unknown Internet protocol discarded.\n"));\r
-}\r
-\r
-\r
-NTSTATUS IPStartup(\r
- PDRIVER_OBJECT DriverObject,\r
- PUNICODE_STRING RegistryPath)\r
-/*\r
- * FUNCTION: Initializes the IP subsystem\r
- * ARGUMENTS:\r
- * DriverObject = Pointer to a driver object for this driver\r
- * RegistryPath = Our registry node for configuration parameters\r
- * RETURNS:\r
- * Status of operation\r
- */\r
-{\r
- UINT i;\r
- LARGE_INTEGER DueTime;\r
-\r
- TI_DbgPrint(MAX_TRACE, ("Called.\n"));\r
-\r
- MaxLLHeaderSize = 0;\r
- MinLLFrameSize = 0;\r
-\r
- /* Start routing subsystem */\r
- RouterStartup();\r
-\r
- /* Start route cache subsystem */\r
- RouteStartup();\r
-\r
- /* Start neighbor cache subsystem */\r
- NBStartup();\r
-\r
- /* Fill the protocol dispatch table with pointers\r
- to the default protocol handler */\r
- for (i = 0; i < IP_PROTOCOL_TABLE_SIZE; i++)\r
- IPRegisterProtocol(i, DefaultProtocolHandler);\r
-\r
- /* Register network level protocol receive handlers */\r
- IPRegisterProtocol(IPPROTO_ICMP, ICMPReceive);\r
-\r
- /* Initialize NTE list and protecting lock */\r
- InitializeListHead(&NetTableListHead);\r
- KeInitializeSpinLock(&NetTableListLock);\r
-\r
- /* Initialize reassembly list and protecting lock */\r
- InitializeListHead(&ReassemblyListHead);\r
- KeInitializeSpinLock(&ReassemblyListLock);\r
-\r
- /* Initialize the prefix list and protecting lock */\r
- InitializeListHead(&PrefixListHead);\r
- KeInitializeSpinLock(&PrefixListLock);\r
-\r
- /* Initialize our periodic timer and its associated DPC object. When the\r
- timer expires, the IPTimeout deferred procedure call (DPC) is queued */\r
- KeInitializeDpc(&IPTimeoutDpc, IPTimeout, NULL);\r
- KeInitializeTimer(&IPTimer);\r
-\r
- /* Start the periodic timer with an initial and periodic\r
- relative expiration time of IP_TIMEOUT milliseconds */\r
- DueTime.QuadPart = -(LONGLONG)IP_TIMEOUT * 10000;\r
- KeSetTimerEx(&IPTimer, DueTime, IP_TIMEOUT, &IPTimeoutDpc);\r
-\r
- IPInitialized = TRUE;\r
-\r
- return STATUS_SUCCESS;\r
-}\r
-\r
-\r
-NTSTATUS IPShutdown(\r
- VOID)\r
-/*\r
- * FUNCTION: Shuts down the IP subsystem\r
- * RETURNS:\r
- * Status of operation\r
- */\r
-{\r
- TI_DbgPrint(MAX_TRACE, ("Called.\n"));\r
-\r
- if (!IPInitialized)\r
- return STATUS_SUCCESS;\r
-\r
- /* Cancel timer */\r
- KeCancelTimer(&IPTimer);\r
-\r
- /* Shutdown neighbor cache subsystem */\r
- NBShutdown();\r
-\r
- /* Shutdown route cache subsystem */\r
- RouteShutdown();\r
-\r
- /* Shutdown routing subsystem */\r
- RouterShutdown();\r
-\r
- IPFreeReassemblyList();\r
-\r
- /* Clear prefix list */\r
- DestroyPLEs();\r
-\r
- IPInitialized = FALSE;\r
-\r
- return STATUS_SUCCESS;\r
-}\r
-\r
-/* EOF */\r
+/*
+ * COPYRIGHT: See COPYING in the top level directory
+ * PROJECT: ReactOS TCP/IP protocol driver
+ * FILE: network/ip.c
+ * PURPOSE: Internet Protocol module
+ * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
+ * REVISIONS:
+ * CSH 01/08-2000 Created
+ */
+#include <tcpip.h>
+#include <ip.h>
+#include <loopback.h>
+#include <neighbor.h>
+#include <receive.h>
+#include <address.h>
+#include <route.h>
+#include <icmp.h>
+#include <pool.h>
+
+
+KTIMER IPTimer;
+KDPC IPTimeoutDpc;
+LIST_ENTRY InterfaceListHead;
+KSPIN_LOCK InterfaceListLock;
+LIST_ENTRY NetTableListHead;
+KSPIN_LOCK NetTableListLock;
+LIST_ENTRY PrefixListHead;
+KSPIN_LOCK PrefixListLock;
+UINT MaxLLHeaderSize; /* Largest maximum header size */
+UINT MinLLFrameSize; /* Largest minimum frame size */
+BOOLEAN IPInitialized = FALSE;
+
+IP_PROTOCOL_HANDLER ProtocolTable[IP_PROTOCOL_TABLE_SIZE];
+
+
+PADDRESS_ENTRY CreateADE(
+ PIP_INTERFACE IF,
+ PIP_ADDRESS Address,
+ UCHAR Type,
+ PNET_TABLE_ENTRY NTE)
+/*
+ * FUNCTION: Creates an address entry and binds it to an interface
+ * ARGUMENTS:
+ * IF = Pointer to interface
+ * Address = Pointer to referenced interface address
+ * Type = Type of address (ADE_*)
+ * NTE = Pointer to net table entry
+ * RETURNS:
+ * Pointer to ADE, NULL if there was not enough free resources
+ * NOTES:
+ * The interface lock must be held when called. The address entry
+ * retains a reference to the provided address and NTE. The caller
+ * is responsible for referencing the these before calling.
+ * As long as you have referenced an ADE you can safely use the
+ * address and NTE as the ADE references both
+ */
+{
+ PADDRESS_ENTRY ADE;
+
+ TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X) Address (0x%X) Type (0x%X) NTE (0x%X).\n",
+ IF, Address, Type, NTE));
+
+ /* Allocate space for an ADE and set it up */
+ ADE = PoolAllocateBuffer(sizeof(ADDRESS_ENTRY));
+ if (!ADE) {
+ TI_DbgPrint(MIN_TRACE, ("Insufficient resources.\n"));
+ return NULL;
+ }
+
+ ADE->RefCount = 1;
+ ADE->NTE = NTE;
+ ADE->Type = Type;
+ ADE->Address = Address;
+
+ /* Add ADE to the list on the interface */
+ InsertTailList(&IF->ADEListHead, &ADE->ListEntry);
+
+ return ADE;
+}
+
+
+VOID DestroyADE(
+ PIP_INTERFACE IF,
+ PADDRESS_ENTRY ADE)
+/*
+ * FUNCTION: Destroys an address entry
+ * ARGUMENTS:
+ * IF = Pointer to interface
+ * ADE = Pointer to address entry
+ * NOTES:
+ * The interface lock must be held when called
+ */
+{
+ TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X) ADE (0x%X).\n", IF, ADE));
+
+ /* Unlink the address entry from the list */
+ RemoveEntryList(&ADE->ListEntry);
+
+ /* Dereference the address */
+ DereferenceObject(ADE->Address);
+
+ /* Dereference the NTE */
+ DereferenceObject(ADE->NTE);
+
+#ifdef DBG
+ ADE->RefCount--;
+
+ if (ADE->RefCount != 0) {
+ TI_DbgPrint(MIN_TRACE, ("Address entry at (0x%X) has (%d) references (should be 0).\n", ADE, ADE->RefCount));
+ }
+#endif
+
+ /* And free the ADE */
+ PoolFreeBuffer(ADE);
+ TI_DbgPrint(MIN_TRACE, ("Check.\n"));
+}
+
+
+VOID DestroyADEs(
+ PIP_INTERFACE IF)
+/*
+ * FUNCTION: Destroys all address entries on an interface
+ * ARGUMENTS:
+ * IF = Pointer to interface
+ * NOTES:
+ * The interface lock must be held when called
+ */
+{
+ PLIST_ENTRY CurrentEntry;
+ PLIST_ENTRY NextEntry;
+ PADDRESS_ENTRY Current;
+
+ TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X).\n", IF));
+
+ /* Search the list and remove every ADE we find */
+ CurrentEntry = IF->ADEListHead.Flink;
+ while (CurrentEntry != &IF->ADEListHead) {
+ NextEntry = CurrentEntry->Flink;
+ Current = CONTAINING_RECORD(CurrentEntry, ADDRESS_ENTRY, ListEntry);
+ /* Destroy the ADE */
+ DestroyADE(IF, Current);
+ CurrentEntry = NextEntry;
+ }
+}
+
+
+PPREFIX_LIST_ENTRY CreatePLE(
+ PIP_INTERFACE IF,
+ PIP_ADDRESS Prefix,
+ UINT Length)
+/*
+ * FUNCTION: Creates a prefix list entry and binds it to an interface
+ * ARGUMENTS:
+ * IF = Pointer to interface
+ * Prefix = Pointer to prefix
+ * Length = Length of prefix
+ * RETURNS:
+ * Pointer to PLE, NULL if there was not enough free resources
+ * NOTES:
+ * The prefix list entry retains a reference to the interface and
+ * the provided address. The caller is responsible for providing
+ * these references
+ */
+{
+ PPREFIX_LIST_ENTRY PLE;
+
+ TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X) Prefix (0x%X) Length (%d).\n", IF, Prefix, Length));
+
+ /* Allocate space for an PLE and set it up */
+ PLE = PoolAllocateBuffer(sizeof(PREFIX_LIST_ENTRY));
+ if (!PLE) {
+ TI_DbgPrint(MIN_TRACE, ("Insufficient resources.\n"));
+ return NULL;
+ }
+
+ PLE->RefCount = 1;
+ PLE->Interface = IF;
+ PLE->Prefix = Prefix;
+ PLE->PrefixLength = Length;
+
+ /* Add PLE to the global prefix list */
+ ExInterlockedInsertTailList(&PrefixListHead, &PLE->ListEntry, &PrefixListLock);
+
+ return PLE;
+}
+
+
+VOID DestroyPLE(
+ PPREFIX_LIST_ENTRY PLE)
+/*
+ * FUNCTION: Destroys an prefix list entry
+ * ARGUMENTS:
+ * PLE = Pointer to prefix list entry
+ * NOTES:
+ * The prefix list lock must be held when called
+ */
+{
+ TI_DbgPrint(DEBUG_IP, ("Called. PLE (0x%X).\n", PLE));
+
+ /* Unlink the prefix list entry from the list */
+ RemoveEntryList(&PLE->ListEntry);
+
+ /* Dereference the address */
+ DereferenceObject(PLE->Prefix);
+
+ /* Dereference the interface */
+ DereferenceObject(PLE->Interface);
+
+#ifdef DBG
+ PLE->RefCount--;
+
+ if (PLE->RefCount != 0) {
+ TI_DbgPrint(MIN_TRACE, ("Prefix list entry at (0x%X) has (%d) references (should be 0).\n", PLE, PLE->RefCount));
+ }
+#endif
+
+ /* And free the PLE */
+ PoolFreeBuffer(PLE);
+}
+
+
+VOID DestroyPLEs(
+ VOID)
+/*
+ * FUNCTION: Destroys all prefix list entries
+ */
+{
+ KIRQL OldIrql;
+ PLIST_ENTRY CurrentEntry;
+ PLIST_ENTRY NextEntry;
+ PPREFIX_LIST_ENTRY Current;
+
+ TI_DbgPrint(DEBUG_IP, ("Called.\n"));
+
+ KeAcquireSpinLock(&PrefixListLock, &OldIrql);
+
+ /* Search the list and remove every PLE we find */
+ CurrentEntry = PrefixListHead.Flink;
+ while (CurrentEntry != &PrefixListHead) {
+ NextEntry = CurrentEntry->Flink;
+ Current = CONTAINING_RECORD(CurrentEntry, PREFIX_LIST_ENTRY, ListEntry);
+ /* Destroy the PLE */
+ DestroyPLE(Current);
+ CurrentEntry = NextEntry;
+ }
+ KeReleaseSpinLock(&PrefixListLock, OldIrql);
+}
+
+
+PNET_TABLE_ENTRY IPCreateNTE(
+ PIP_INTERFACE IF,
+ PIP_ADDRESS Address,
+ UINT PrefixLength)
+/*
+ * FUNCTION: Creates a net table entry and binds it to an interface
+ * ARGUMENTS:
+ * IF = Pointer to interface
+ * Address = Pointer to interface address
+ * PrefixLength = Length of prefix
+ * RETURNS:
+ * Pointer to NTE, NULL if there was not enough free resources
+ * NOTES:
+ * The interface lock must be held when called.
+ * The net table entry retains a reference to the interface and
+ * the provided address. The caller is responsible for providing
+ * these references
+ */
+{
+ PNET_TABLE_ENTRY NTE;
+ PADDRESS_ENTRY ADE;
+
+ TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X) Address (0x%X) PrefixLength (%d).\n", IF, Address, PrefixLength));
+
+ /* Allocate room for an NTE */
+ NTE = PoolAllocateBuffer(sizeof(NET_TABLE_ENTRY));
+ if (!NTE) {
+ TI_DbgPrint(MIN_TRACE, ("Insufficient resources.\n"));
+ return NULL;
+ }
+
+ NTE->Interface = IF;
+
+ /* One reference is for beeing alive and one reference is for the ADE */
+ NTE->RefCount = 2;
+
+ NTE->Address = Address;
+ /* One reference is for NTE, one reference is given to the
+ address entry, and one reference is given to the prefix
+ list entry */
+ ReferenceObject(Address);
+ ReferenceObject(Address);
+ ReferenceObject(Address);
+
+ /* Create an address entry and add it to the list */
+ ADE = CreateADE(IF, NTE->Address, ADE_UNICAST, NTE);
+ if (!ADE) {
+ TI_DbgPrint(MIN_TRACE, ("Insufficient resources.\n"));
+ PoolFreeBuffer(NTE);
+ return NULL;
+ }
+
+ /* Create a prefix list entry for unicast address */
+ NTE->PLE = CreatePLE(IF, NTE->Address, PrefixLength);
+ if (!NTE->PLE) {
+ DestroyADE(IF, ADE);
+ PoolFreeBuffer(NTE);
+ return NULL;
+ }
+
+ /* Reference the interface for the prefix list entry */
+ ReferenceObject(IF);
+
+ /* Add NTE to the list on the interface */
+ InsertTailList(&IF->NTEListHead, &NTE->IFListEntry);
+
+ /* Add NTE to the global net table list */
+ ExInterlockedInsertTailList(&NetTableListHead, &NTE->NTListEntry, &NetTableListLock);
+
+ return NTE;
+}
+
+
+VOID DestroyNTE(
+ PIP_INTERFACE IF,
+ PNET_TABLE_ENTRY NTE)
+/*
+ * FUNCTION: Destroys a net table entry
+ * ARGUMENTS:
+ * IF = Pointer to interface
+ * NTE = Pointer to net table entry
+ * NOTES:
+ * The net table list lock must be held when called
+ * The interface lock must be held when called
+ */
+{
+ KIRQL OldIrql;
+
+ TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X) NTE (0x%X).\n", IF, NTE));
+
+ /* Invalidate the prefix list entry for this NTE */
+ KeAcquireSpinLock(&PrefixListLock, &OldIrql);
+ DestroyPLE(NTE->PLE);
+ KeReleaseSpinLock(&PrefixListLock, OldIrql);
+
+ /* Remove NTE from the interface list */
+ RemoveEntryList(&NTE->IFListEntry);
+ /* Remove NTE from the net table list */
+ RemoveEntryList(&NTE->NTListEntry);
+ /* Dereference the objects that are referenced */
+ DereferenceObject(NTE->Address);
+ DereferenceObject(NTE->Interface);
+#ifdef DBG
+ NTE->RefCount--;
+
+ if (NTE->RefCount != 0) {
+ TI_DbgPrint(MIN_TRACE, ("Net table entry at (0x%X) has (%d) references (should be 0).\n", NTE, NTE->RefCount));
+ }
+#endif
+ /* And free the NTE */
+ PoolFreeBuffer(NTE);
+}
+
+
+VOID DestroyNTEs(
+ PIP_INTERFACE IF)
+/*
+ * FUNCTION: Destroys all net table entries on an interface
+ * ARGUMENTS:
+ * IF = Pointer to interface
+ * NOTES:
+ * The net table list lock must be held when called
+ * The interface lock may be held when called
+ */
+{
+ PLIST_ENTRY CurrentEntry;
+ PLIST_ENTRY NextEntry;
+ PNET_TABLE_ENTRY Current;
+
+ TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X).\n", IF));
+
+ /* Search the list and remove every NTE we find */
+ CurrentEntry = IF->NTEListHead.Flink;
+ while (CurrentEntry != &IF->NTEListHead) {
+ NextEntry = CurrentEntry->Flink;
+ Current = CONTAINING_RECORD(CurrentEntry, NET_TABLE_ENTRY, IFListEntry);
+ /* Destroy the NTE */
+ DestroyNTE(IF, Current);
+ CurrentEntry = NextEntry;
+ }
+}
+
+
+PNET_TABLE_ENTRY IPLocateNTEOnInterface(
+ PIP_INTERFACE IF,
+ PIP_ADDRESS Address,
+ PUINT AddressType)
+/*
+ * FUNCTION: Locates an NTE on an interface
+ * ARGUMENTS:
+ * IF = Pointer to interface
+ * Address = Pointer to IP address
+ * AddressType = Address of type of IP address
+ * NOTES:
+ * If found, the NTE is referenced for the caller. The caller is
+ * responsible for dereferencing after use
+ * RETURNS:
+ * Pointer to net table entry, NULL if none was found
+ */
+{
+ KIRQL OldIrql;
+ PLIST_ENTRY CurrentEntry;
+ PADDRESS_ENTRY Current;
+
+ TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X) Address (0x%X) AddressType (0x%X).\n",
+ IF, Address, AddressType));
+
+ KeAcquireSpinLock(&IF->Lock, &OldIrql);
+
+ /* Search the list and return the NTE if found */
+ CurrentEntry = IF->ADEListHead.Flink;
+ while (CurrentEntry != &IF->ADEListHead) {
+ Current = CONTAINING_RECORD(CurrentEntry, ADDRESS_ENTRY, ListEntry);
+ if (AddrIsEqual(Address, Current->Address)) {
+ ReferenceObject(Current->NTE);
+ *AddressType = Current->Type;
+ KeReleaseSpinLock(&IF->Lock, OldIrql);
+ return Current->NTE;
+ }
+ CurrentEntry = CurrentEntry->Flink;
+ }
+
+ KeReleaseSpinLock(&IF->Lock, OldIrql);
+
+ return NULL;
+}
+
+
+PNET_TABLE_ENTRY IPLocateNTE(
+ PIP_ADDRESS Address,
+ PUINT AddressType)
+/*
+ * FUNCTION: Locates an NTE for the network Address is on
+ * ARGUMENTS:
+ * Address = Pointer to an address to find associated NTE of
+ * AddressType = Address of address type
+ * NOTES:
+ * If found the NTE is referenced for the caller. The caller is
+ * responsible for dereferencing after use
+ * RETURNS:
+ * Pointer to NTE if the address was found, NULL if not.
+ */
+{
+ KIRQL OldIrql;
+ PLIST_ENTRY CurrentEntry;
+ PNET_TABLE_ENTRY Current;
+ PNET_TABLE_ENTRY NTE;
+
+ TI_DbgPrint(DEBUG_IP, ("Called. Address (0x%X) AddressType (0x%X).\n",
+ Address, AddressType));
+
+ KeAcquireSpinLock(&NetTableListLock, &OldIrql);
+
+ /* Search the list and return the NTE if found */
+ CurrentEntry = NetTableListHead.Flink;
+ while (CurrentEntry != &NetTableListHead) {
+ Current = CONTAINING_RECORD(CurrentEntry, NET_TABLE_ENTRY, NTListEntry);
+ NTE = IPLocateNTEOnInterface(Current->Interface, Address, AddressType);
+ if (NTE) {
+ ReferenceObject(NTE);
+ KeReleaseSpinLock(&NetTableListLock, OldIrql);
+ return NTE;
+ }
+ CurrentEntry = CurrentEntry->Flink;
+ }
+
+ KeReleaseSpinLock(&NetTableListLock, OldIrql);
+
+ return NULL;
+}
+
+
+PADDRESS_ENTRY IPLocateADE(
+ PIP_ADDRESS Address,
+ UINT AddressType)
+/*
+ * FUNCTION: Locates an ADE for the address
+ * ARGUMENTS:
+ * Address = Pointer to an address to find associated ADE of
+ * AddressType = Type of address
+ * RETURNS:
+ * Pointer to ADE if the address was found, NULL if not.
+ * NOTES:
+ * If found the ADE is referenced for the caller. The caller is
+ * responsible for dereferencing after use
+ */
+{
+ KIRQL OldIrql;
+ PLIST_ENTRY CurrentIFEntry;
+ PLIST_ENTRY CurrentADEEntry;
+ PIP_INTERFACE CurrentIF;
+ PADDRESS_ENTRY CurrentADE;
+
+ TI_DbgPrint(DEBUG_IP, ("Called. Address (0x%X) AddressType (0x%X).\n",
+ Address, AddressType));
+
+ KeAcquireSpinLock(&InterfaceListLock, &OldIrql);
+
+ /* Search the interface list */
+ CurrentIFEntry = InterfaceListHead.Flink;
+ while (CurrentIFEntry != &InterfaceListHead) {
+ CurrentIF = CONTAINING_RECORD(CurrentIFEntry, IP_INTERFACE, ListEntry);
+
+ /* Search the address entry list and return the ADE if found */
+ CurrentADEEntry = CurrentIF->ADEListHead.Flink;
+ while (CurrentADEEntry != &CurrentIF->ADEListHead) {
+ CurrentADE = CONTAINING_RECORD(CurrentADEEntry, ADDRESS_ENTRY, ListEntry);
+ if ((AddrIsEqual(Address, CurrentADE->Address)) &&
+ (CurrentADE->Type == AddressType)) {
+ ReferenceObject(CurrentADE);
+ KeReleaseSpinLock(&InterfaceListLock, OldIrql);
+ return CurrentADE;
+ }
+ CurrentADEEntry = CurrentADEEntry->Flink;
+ }
+ CurrentIFEntry = CurrentIFEntry->Flink;
+ }
+
+ KeReleaseSpinLock(&InterfaceListLock, OldIrql);
+
+ return NULL;
+}
+
+
+PADDRESS_ENTRY IPGetDefaultADE(
+ UINT AddressType)
+/*
+ * FUNCTION: Returns a default address entry
+ * ARGUMENTS:
+ * AddressType = Type of address
+ * RETURNS:
+ * Pointer to ADE if found, NULL if not.
+ * NOTES:
+ * Loopback interface is only considered if it is the only interface.
+ * If found, the address entry is referenced
+ */
+{
+ KIRQL OldIrql;
+ PLIST_ENTRY CurrentIFEntry;
+ PLIST_ENTRY CurrentADEEntry;
+ PIP_INTERFACE CurrentIF;
+ PADDRESS_ENTRY CurrentADE;
+#if 0
+ BOOLEAN LoopbackIsRegistered = FALSE;
+#endif
+ TI_DbgPrint(DEBUG_IP, ("Called. AddressType (0x%X).\n", AddressType));
+
+ KeAcquireSpinLock(&InterfaceListLock, &OldIrql);
+
+ /* Search the interface list */
+ CurrentIFEntry = InterfaceListHead.Flink;
+ while (CurrentIFEntry != &InterfaceListHead) {
+ CurrentIF = CONTAINING_RECORD(CurrentIFEntry, IP_INTERFACE, ListEntry);
+#if 0
+ if (CurrentIF != Loopback) {
+#endif
+ /* Search the address entry list and return the first appropriate ADE found */
+ CurrentADEEntry = CurrentIF->ADEListHead.Flink;
+ while (CurrentADEEntry != &CurrentIF->ADEListHead) {
+ CurrentADE = CONTAINING_RECORD(CurrentADEEntry, ADDRESS_ENTRY, ListEntry);
+ if (CurrentADE->Type == AddressType)
+ ReferenceObject(CurrentADE);
+ KeReleaseSpinLock(&InterfaceListLock, OldIrql);
+ return CurrentADE;
+ }
+ CurrentADEEntry = CurrentADEEntry->Flink;
+#if 0
+ } else
+ LoopbackIsRegistered = TRUE;
+#endif
+ CurrentIFEntry = CurrentIFEntry->Flink;
+ }
+#if 0
+ /* No address was found. Use loopback interface if available */
+ if (LoopbackIsRegistered) {
+ CurrentADEEntry = Loopback->ADEListHead.Flink;
+ while (CurrentADEEntry != &Loopback->ADEListHead) {
+ CurrentADE = CONTAINING_RECORD(CurrentADEEntry, ADDRESS_ENTRY, ListEntry);
+ if (CurrentADE->Type == AddressType) {
+ ReferenceObject(CurrentADE);
+ KeReleaseSpinLock(&InterfaceListLock, OldIrql);
+ return CurrentADE;
+ }
+ CurrentADEEntry = CurrentADEEntry->Flink;
+ }
+ }
+#endif
+ KeReleaseSpinLock(&InterfaceListLock, OldIrql);
+
+ return NULL;
+}
+
+
+VOID IPTimeout(
+ PKDPC Dpc,
+ PVOID DeferredContext,
+ PVOID SystemArgument1,
+ PVOID SystemArgument2)
+/*
+ * FUNCTION: Timeout DPC
+ * ARGUMENTS:
+ * Dpc = Pointer to our DPC object
+ * DeferredContext = Pointer to context information (unused)
+ * SystemArgument1 = Unused
+ * SystemArgument2 = Unused
+ * NOTES:
+ * This routine is dispatched once in a while to do maintainance jobs
+ */
+{
+ /* Check if datagram fragments have taken too long to assemble */
+ IPDatagramReassemblyTimeout();
+
+ /* Clean possible outdated cached neighbor addresses */
+ NBTimeout();
+}
+
+
+VOID IPDispatchProtocol(
+ PNET_TABLE_ENTRY NTE,
+ PIP_PACKET IPPacket)
+/*
+ * FUNCTION: IP protocol dispatcher
+ * ARGUMENTS:
+ * NTE = Pointer to net table entry which the packet was received on
+ * IPPacket = Pointer to an IP packet that was received
+ * NOTES:
+ * This routine examines the IP header and passes the packet on to the
+ * right upper level protocol receive handler
+ */
+{
+ UINT Protocol;
+
+ switch (IPPacket->Type) {
+ case IP_ADDRESS_V4:
+ Protocol = ((PIPv4_HEADER)(IPPacket->Header))->Protocol;
+ break;
+ case IP_ADDRESS_V6:
+ /* FIXME: IPv6 adresses not supported */
+ TI_DbgPrint(MIN_TRACE, ("IPv6 datagram discarded.\n"));
+ return;
+ default:
+ Protocol = 0;
+ }
+
+ /* Call the appropriate protocol handler */
+ (*ProtocolTable[Protocol])(NTE, IPPacket);
+}
+
+
+PIP_INTERFACE IPCreateInterface(
+ PLLIP_BIND_INFO BindInfo)
+/*
+ * FUNCTION: Creates an IP interface
+ * ARGUMENTS:
+ * BindInfo = Pointer to link layer to IP binding information
+ * RETURNS:
+ * Pointer to IP_INTERFACE structure, NULL if there was
+ * not enough free resources
+ */
+{
+ PIP_INTERFACE IF;
+
+ TI_DbgPrint(DEBUG_IP, ("Called. BindInfo (0x%X).\n", BindInfo));
+
+ IF = PoolAllocateBuffer(sizeof(IP_INTERFACE));
+ if (!IF) {
+ TI_DbgPrint(MIN_TRACE, ("Insufficient resources.\n"));
+ return NULL;
+ }
+
+ IF->RefCount = 1;
+ IF->Context = BindInfo->Context;
+ IF->HeaderSize = BindInfo->HeaderSize;
+ if (IF->HeaderSize > MaxLLHeaderSize)
+ MaxLLHeaderSize = IF->HeaderSize;
+
+ IF->MinFrameSize = BindInfo->MinFrameSize;
+ if (IF->MinFrameSize > MinLLFrameSize)
+ MinLLFrameSize = IF->MinFrameSize;
+
+ IF->MTU = BindInfo->MTU;
+ IF->Address = BindInfo->Address;
+ IF->AddressLength = BindInfo->AddressLength;
+ IF->Transmit = BindInfo->Transmit;
+
+ InitializeListHead(&IF->ADEListHead);
+ InitializeListHead(&IF->NTEListHead);
+
+ KeInitializeSpinLock(&IF->Lock);
+
+ return IF;
+}
+
+
+VOID IPDestroyInterface(
+ PIP_INTERFACE IF)
+/*
+ * FUNCTION: Destroys an IP interface
+ * ARGUMENTS:
+ * IF = Pointer to interface to destroy
+ */
+{
+ KIRQL OldIrql1;
+ KIRQL OldIrql2;
+
+ TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X).\n", IF));
+
+ KeAcquireSpinLock(&NetTableListLock, &OldIrql1);
+ KeAcquireSpinLock(&IF->Lock, &OldIrql2);
+ DestroyADEs(IF);
+ DestroyNTEs(IF);
+ KeReleaseSpinLock(&IF->Lock, OldIrql2);
+ KeReleaseSpinLock(&NetTableListLock, OldIrql1);
+
+#ifdef DBG
+ IF->RefCount--;
+
+ if (IF->RefCount != 0) {
+ TI_DbgPrint(MIN_TRACE, ("Interface at (0x%X) has (%d) references (should be 0).\n", IF, IF->RefCount));
+ }
+#endif
+ PoolFreeBuffer(IF);
+}
+
+
+BOOLEAN IPRegisterInterface(
+ PIP_INTERFACE IF)
+/*
+ * FUNCTION: Registers an IP interface with IP layer
+ * ARGUMENTS:
+ * IF = Pointer to interface to register
+ * RETURNS;
+ * TRUE if interface was successfully registered, FALSE if not
+ */
+{
+ KIRQL OldIrql;
+ PLIST_ENTRY CurrentEntry;
+ PNET_TABLE_ENTRY Current;
+ PROUTE_CACHE_NODE RCN;
+ PNEIGHBOR_CACHE_ENTRY NCE;
+
+ TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X).\n", IF));
+
+ KeAcquireSpinLock(&IF->Lock, &OldIrql);
+
+ /* Add routes to all NTEs on this interface */
+ CurrentEntry = IF->NTEListHead.Flink;
+ while (CurrentEntry != &IF->NTEListHead) {
+ Current = CONTAINING_RECORD(CurrentEntry, NET_TABLE_ENTRY, IFListEntry);
+
+ /* Add a permanent neighbor for this NTE */
+ ReferenceObject(Current->Address);
+ NCE = NBAddNeighbor(IF, Current->Address, IF->Address,
+ IF->AddressLength, NUD_PERMANENT);
+ if (!NCE) {
+ TI_DbgPrint(MIN_TRACE, ("Could not create NCE.\n"));
+ DereferenceObject(Current->Address);
+ KeReleaseSpinLock(&IF->Lock, OldIrql);
+ return FALSE;
+ }
+ RCN = RouteAddRouteToDestination(Current->Address, Current, IF, NCE);
+ if (!RCN) {
+ TI_DbgPrint(MIN_TRACE, ("Could not create RCN.\n"));
+ DereferenceObject(Current->Address);
+ KeReleaseSpinLock(&IF->Lock, OldIrql);
+ return FALSE;
+ }
+ /* Don't need this any more since the route cache references the NCE */
+ DereferenceObject(NCE);
+
+ CurrentEntry = CurrentEntry->Flink;
+ }
+
+ /* Add interface to the global interface list */
+ ExInterlockedInsertTailList(&InterfaceListHead, &IF->ListEntry, &InterfaceListLock);
+
+ KeReleaseSpinLock(&IF->Lock, OldIrql);
+
+ return TRUE;
+}
+
+
+VOID IPUnregisterInterface(
+ PIP_INTERFACE IF)
+/*
+ * FUNCTION: Unregisters an IP interface with IP layer
+ * ARGUMENTS:
+ * IF = Pointer to interface to unregister
+ */
+{
+ KIRQL OldIrql1;
+ KIRQL OldIrql2;
+ KIRQL OldIrql3;
+ PLIST_ENTRY CurrentEntry;
+ PNET_TABLE_ENTRY Current;
+ PNEIGHBOR_CACHE_ENTRY NCE;
+
+ TI_DbgPrint(DEBUG_IP, ("Called. IF (0x%X).\n", IF));
+
+ KeAcquireSpinLock(&NetTableListLock, &OldIrql1);
+ KeAcquireSpinLock(&IF->Lock, &OldIrql2);
+
+ /* Remove routes to all NTEs on this interface */
+ CurrentEntry = IF->NTEListHead.Flink;
+ while (CurrentEntry != &IF->NTEListHead) {
+ Current = CONTAINING_RECORD(CurrentEntry, NET_TABLE_ENTRY, IFListEntry);
+
+ /* Remove NTE from global net table list */
+ RemoveEntryList(&Current->NTListEntry);
+
+ /* Remove all references from route cache to NTE */
+ RouteInvalidateNTE(Current);
+
+ /* Remove permanent NCE, but first we have to find it */
+ NCE = NBLocateNeighbor(Current->Address);
+ if (NCE) {
+ DereferenceObject(NCE);
+ NBRemoveNeighbor(NCE);
+ }
+
+ CurrentEntry = CurrentEntry->Flink;
+ }
+
+ KeAcquireSpinLock(&InterfaceListLock, &OldIrql3);
+ /* Ouch...three spinlocks acquired! Fortunately
+ we don't unregister interfaces very often */
+ RemoveEntryList(&IF->ListEntry);
+ KeReleaseSpinLock(&InterfaceListLock, OldIrql3);
+
+ KeReleaseSpinLock(&IF->Lock, OldIrql2);
+ KeReleaseSpinLock(&NetTableListLock, OldIrql1);
+}
+
+
+VOID IPRegisterProtocol(
+ UINT ProtocolNumber,
+ IP_PROTOCOL_HANDLER Handler)
+/*
+ * FUNCTION: Registers a handler for an IP protocol number
+ * ARGUMENTS:
+ * ProtocolNumber = Internet Protocol number for which to register handler
+ * Handler = Pointer to handler to be called when a packet is received
+ * NOTES:
+ * To unregister a protocol handler, call this function with Handler = NULL
+ */
+{
+#ifdef DBG
+ if (ProtocolNumber >= IP_PROTOCOL_TABLE_SIZE)
+ TI_DbgPrint(MIN_TRACE, ("Protocol number is out of range (%d).\n", ProtocolNumber));
+#endif
+
+ ProtocolTable[ProtocolNumber] = Handler;
+}
+
+
+VOID DefaultProtocolHandler(
+ PNET_TABLE_ENTRY NTE,
+ PIP_PACKET IPPacket)
+/*
+ * FUNCTION: Default handler for Internet protocols
+ * ARGUMENTS:
+ * NTE = Pointer to net table entry which the packet was received on
+ * IPPacket = Pointer to an IP packet that was received
+ */
+{
+ TI_DbgPrint(MID_TRACE, ("Packet of unknown Internet protocol discarded.\n"));
+}
+
+
+NTSTATUS IPStartup(
+ PDRIVER_OBJECT DriverObject,
+ PUNICODE_STRING RegistryPath)
+/*
+ * FUNCTION: Initializes the IP subsystem
+ * ARGUMENTS:
+ * DriverObject = Pointer to a driver object for this driver
+ * RegistryPath = Our registry node for configuration parameters
+ * RETURNS:
+ * Status of operation
+ */
+{
+ UINT i;
+ LARGE_INTEGER DueTime;
+
+ TI_DbgPrint(MAX_TRACE, ("Called.\n"));
+
+ MaxLLHeaderSize = 0;
+ MinLLFrameSize = 0;
+
+ /* Start routing subsystem */
+ RouterStartup();
+
+ /* Start route cache subsystem */
+ RouteStartup();
+
+ /* Start neighbor cache subsystem */
+ NBStartup();
+
+ /* Fill the protocol dispatch table with pointers
+ to the default protocol handler */
+ for (i = 0; i < IP_PROTOCOL_TABLE_SIZE; i++)
+ IPRegisterProtocol(i, DefaultProtocolHandler);
+
+ /* Register network level protocol receive handlers */
+ IPRegisterProtocol(IPPROTO_ICMP, ICMPReceive);
+
+ /* Initialize NTE list and protecting lock */
+ InitializeListHead(&NetTableListHead);
+ KeInitializeSpinLock(&NetTableListLock);
+
+ /* Initialize reassembly list and protecting lock */
+ InitializeListHead(&ReassemblyListHead);
+ KeInitializeSpinLock(&ReassemblyListLock);
+
+ /* Initialize the prefix list and protecting lock */
+ InitializeListHead(&PrefixListHead);
+ KeInitializeSpinLock(&PrefixListLock);
+
+ /* Initialize our periodic timer and its associated DPC object. When the
+ timer expires, the IPTimeout deferred procedure call (DPC) is queued */
+ KeInitializeDpc(&IPTimeoutDpc, IPTimeout, NULL);
+ KeInitializeTimer(&IPTimer);
+
+ /* Start the periodic timer with an initial and periodic
+ relative expiration time of IP_TIMEOUT milliseconds */
+ DueTime.QuadPart = -(LONGLONG)IP_TIMEOUT * 10000;
+ KeSetTimerEx(&IPTimer, DueTime, IP_TIMEOUT, &IPTimeoutDpc);
+
+ IPInitialized = TRUE;
+
+ return STATUS_SUCCESS;
+}
+
+
+NTSTATUS IPShutdown(
+ VOID)
+/*
+ * FUNCTION: Shuts down the IP subsystem
+ * RETURNS:
+ * Status of operation
+ */
+{
+ TI_DbgPrint(MAX_TRACE, ("Called.\n"));
+
+ if (!IPInitialized)
+ return STATUS_SUCCESS;
+
+ /* Cancel timer */
+ KeCancelTimer(&IPTimer);
+
+ /* Shutdown neighbor cache subsystem */
+ NBShutdown();
+
+ /* Shutdown route cache subsystem */
+ RouteShutdown();
+
+ /* Shutdown routing subsystem */
+ RouterShutdown();
+
+ IPFreeReassemblyList();
+
+ /* Clear prefix list */
+ DestroyPLEs();
+
+ IPInitialized = FALSE;
+
+ return STATUS_SUCCESS;
+}
+
+/* EOF */