/*++ Copyright (c) 1997 Microsoft Corporation Module: timer.c Abstract: Contains code for the NAT's periodic-timer routine. Author: Abolade Gbadegesin (t-abolag) 22-July-1997 Revision History: --*/ #include "precomp.h" #pragma hdrstop // // Defines the interval at which the timer fires, in 100-nanosecond intervals // #define TIMER_INTERVAL (60 * 1000 * 1000 * 10) // // DPC object for stress-triggered invocations of NatTimerRoutine // KDPC CleanupDpcObject; // // Flag indicating whether stress-triggered cleanup has been scheduled. // ULONG CleanupDpcPending; // // Return-value of KeQueryTimeIncrement, used for normalizing tick-counts // ULONG TimeIncrement; // // DPC object for NatTimerRoutine // KDPC TimerDpcObject; // // Timer object for NatTimerRoutine // KTIMER TimerObject; // // FORWARD DECLARATIONS // VOID NatCleanupDpcRoutine( PKDPC Dpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2 ); VOID NatTimerRoutine( PKDPC Dpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2 ); VOID NatCleanupDpcRoutine( PKDPC Dpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2 ) { KIRQL Irql; PLIST_ENTRY Link; PNAT_DYNAMIC_MAPPING Mapping; KeAcquireSpinLock(&MappingLock, &Irql); for (Link = MappingList.Flink; Link != &MappingList; Link = Link->Flink) { Mapping = CONTAINING_RECORD(Link, NAT_DYNAMIC_MAPPING, Link); if (NAT_MAPPING_EXPIRED(Mapping)) { Link = Link->Blink; NatDeleteMapping(Mapping); } } KeReleaseSpinLock(&MappingLock, Irql); InterlockedExchange(&CleanupDpcPending, FALSE); } VOID NatInitializeTimerManagement( VOID ) /*++ Routine Description: This routine is called to initialize the timer-management module, in preparation for active operation. Arguments: none. Return Value: none. --*/ { CALLTRACE(("NatInitializeTimerManagement\n")); TimeIncrement = KeQueryTimeIncrement(); KeInitializeDpc(&TimerDpcObject, NatTimerRoutine, NULL); KeInitializeTimer(&TimerObject); CleanupDpcPending = FALSE; KeInitializeDpc(&CleanupDpcObject, NatCleanupDpcRoutine, NULL); } // NatInitializeTimerManagement VOID NatShutdownTimerManagement( VOID ) /*++ Routine Description: This routine is called to shutdown the timer management module. Arguments: none. Return Value: none. --*/ { CALLTRACE(("NatShutdownTimerManagement\n")); NatStopTimer(); } // NatShutdownTimerManagement VOID NatStartTimer( VOID ) /*++ Routine Description: This routine is invoked to start the periodic timer. Arguments: none. Return Value: none. --*/ { LARGE_INTEGER DueTime; // // Launch the periodic timer // DueTime.LowPart = TIMER_INTERVAL; DueTime.HighPart = 0; DueTime = RtlLargeIntegerNegate(DueTime); KeSetTimerEx( &TimerObject, DueTime, TIMER_INTERVAL / 10000, &TimerDpcObject ); } VOID NatStopTimer( VOID ) /*++ Routine Description: This routine is invoked to stop the periodic timer. Arguments: none. Return Value: none. --*/ { KeCancelTimer(&TimerObject); } VOID NatTimerRoutine( PKDPC Dpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2 ) /*++ Routine Description: This routine is invoked periodically to garbage-collect expired mappings. Arguments: Dpc - associated DPC object DeferredContext - unused. SystemArgument1 - unused. SystemArgument2 - unused. Return Value: none. --*/ { LONG64 CurrentTime; PNAT_EDITOR Editor; PNAT_ICMP_MAPPING IcmpMapping; PNAT_INTERFACE Interfacep; PNAT_IP_MAPPING IpMapping; KIRQL Irql; PLIST_ENTRY Link; PNAT_DYNAMIC_MAPPING Mapping; PNAT_PPTP_MAPPING PptpMapping; UCHAR Protocol; PRTL_SPLAY_LINKS SLink; PNAT_TICKET Ticketp; LONG64 Timeout; LONG64 TcpMinAccessTime; LONG64 UdpMinAccessTime; CALLTRACE(("NatTimerRoutine\n")); // // Compute the minimum values allowed in TCP/UDP 'LastAccessTime' fields; // any mappings last accessed before these thresholds will be eliminated. // KeQueryTickCount((PLARGE_INTEGER)&CurrentTime); TcpMinAccessTime = CurrentTime - SECONDS_TO_TICKS(TcpTimeoutSeconds); UdpMinAccessTime = CurrentTime - SECONDS_TO_TICKS(UdpTimeoutSeconds); // // Update mapping statistics and clean out expired mappings, // using the above precomputed minimum access times // KeAcquireSpinLock(&MappingLock, &Irql); for (Link = MappingList.Flink; Link != &MappingList; Link = Link->Flink) { Mapping = CONTAINING_RECORD(Link, NAT_DYNAMIC_MAPPING, Link); NatUpdateStatisticsMapping(Mapping); // // Don't check for expiration if the mapping is marked no-timeout; // however, if it is detached from its director, then go ahead // with the expiration-check. // if (!NAT_MAPPING_EXPIRED(Mapping) && NAT_MAPPING_NO_TIMEOUT(Mapping) && Mapping->Director) { continue; } // // See if the mapping has expired // KeAcquireSpinLockAtDpcLevel(&Mapping->Lock); Protocol = MAPPING_PROTOCOL(Mapping->SourceKey[NatForwardPath]); if (!NAT_MAPPING_EXPIRED(Mapping)) { // // The mapping is not explicitly marked for expiration; // see if its last access time is too long ago // if (Protocol == NAT_PROTOCOL_TCP) { if (!NAT_MAPPING_INBOUND(Mapping)) { if ((Mapping->Flags & NAT_MAPPING_FLAG_FWD_SYN) && !(Mapping->Flags & NAT_MAPPING_FLAG_REV_SYN)) { // // This is an outbound connection for which we've seen // the outbound SYN (which means we've been tracking // it from the beginning), but not an inbound SYN. We // want to use a smaller timeout here so that we may // reclaim memory for mappings created for connection // attempts to non-existant servers. (A large number // of these types of mappings would exist if a machine // on the private network is performing some sort of a // network scan; e.g., a machine infected w/ nimda.) // if (Mapping->LastAccessTime >= UdpMinAccessTime) { KeReleaseSpinLockFromDpcLevel(&Mapping->Lock); continue; } } else if (Mapping->LastAccessTime >= TcpMinAccessTime) { KeReleaseSpinLockFromDpcLevel(&Mapping->Lock); continue; } } else if (!NAT_MAPPING_TCP_OPEN(Mapping)) { // // This is an inbound connection for which we have not // yet completed the 3-way handshake. We want to use // a shorter timeout here to reduce memory consumption // in those cases where someone is performing a synflood // against us. // if (Mapping->LastAccessTime >= UdpMinAccessTime) { KeReleaseSpinLockFromDpcLevel(&Mapping->Lock); continue; } } else if (Mapping->LastAccessTime >= TcpMinAccessTime) { KeReleaseSpinLockFromDpcLevel(&Mapping->Lock); continue; } } else if (Mapping->LastAccessTime >= UdpMinAccessTime) { KeReleaseSpinLockFromDpcLevel(&Mapping->Lock); continue; } } KeReleaseSpinLockFromDpcLevel(&Mapping->Lock); // // The mapping has expired; remove it // TRACE( MAPPING, ( "NatTimerRoutine: >Source,Destination=%016I64X:%016I64X\n", Mapping->SourceKey[NatForwardPath], Mapping->DestinationKey[NatForwardPath] )); TRACE( MAPPING, ( "NatTimerRoutine: SourceKey[NatReversePath], Mapping->DestinationKey[NatReversePath] )); Link = Link->Blink; NatDeleteMapping(Mapping); } KeReleaseSpinLockFromDpcLevel(&MappingLock); // // Traverse the PPTP-mapping list and remove all expired entries. // KeAcquireSpinLockAtDpcLevel(&PptpMappingLock); for (Link = PptpMappingList[NatInboundDirection].Flink; Link != &PptpMappingList[NatInboundDirection]; Link = Link->Flink) { PptpMapping = CONTAINING_RECORD( Link, NAT_PPTP_MAPPING, Link[NatInboundDirection] ); if (!NAT_PPTP_DISCONNECTED(PptpMapping) || PptpMapping->LastAccessTime >= UdpMinAccessTime) { continue; } Link = Link->Blink; RemoveEntryList(&PptpMapping->Link[NatInboundDirection]); RemoveEntryList(&PptpMapping->Link[NatOutboundDirection]); TRACE( MAPPING, ("NatTimerRoutine: Pptp=%016I64X:%016I64X:%d:%d:%d\n", PptpMapping->PrivateKey, PptpMapping->PublicKey, PptpMapping->PrivateCallId, PptpMapping->PublicCallId, PptpMapping->RemoteCallId )); FREE_PPTP_BLOCK(PptpMapping); } KeReleaseSpinLockFromDpcLevel(&PptpMappingLock); // // Traverse the ICMP-mapping list and remove each expired entry. // KeAcquireSpinLockAtDpcLevel(&IcmpMappingLock); for (Link = IcmpMappingList[NatInboundDirection].Flink; Link != &IcmpMappingList[NatInboundDirection]; Link = Link->Flink) { IcmpMapping = CONTAINING_RECORD( Link, NAT_ICMP_MAPPING, Link[NatInboundDirection] ); if (IcmpMapping->LastAccessTime >= UdpMinAccessTime) { continue; } Link = Link->Blink; RemoveEntryList(&IcmpMapping->Link[NatInboundDirection]); RemoveEntryList(&IcmpMapping->Link[NatOutboundDirection]); TRACE( MAPPING, ("NatTimerRoutine: Icmp=%016I64X:%04X::%016I64X:%04X\n", IcmpMapping->PrivateKey, IcmpMapping->PrivateId, IcmpMapping->PublicKey, IcmpMapping->PublicId )); FREE_ICMP_BLOCK(IcmpMapping); } KeReleaseSpinLockFromDpcLevel(&IcmpMappingLock); // // Traverse the interface's IP-mapping list // and remove each expired entry. // KeAcquireSpinLockAtDpcLevel(&IpMappingLock); for (Link = IpMappingList[NatInboundDirection].Flink; Link != &IpMappingList[NatInboundDirection]; Link = Link->Flink) { IpMapping = CONTAINING_RECORD( Link, NAT_IP_MAPPING, Link[NatInboundDirection] ); if (IpMapping->LastAccessTime >= UdpMinAccessTime) { continue; } Link = Link->Blink; RemoveEntryList(&IpMapping->Link[NatInboundDirection]); RemoveEntryList(&IpMapping->Link[NatOutboundDirection]); TRACE( MAPPING, ( "NatTimerRoutine: Ip=%d:%016I64X:%016I64X\n", IpMapping->Protocol, IpMapping->PrivateKey, IpMapping->PublicKey )); FREE_IP_BLOCK(IpMapping); } KeReleaseSpinLockFromDpcLevel(&IpMappingLock); // // Garbage collect all interfaces' structures // KeAcquireSpinLockAtDpcLevel(&InterfaceLock); for (Link = InterfaceList.Flink; Link != &InterfaceList; Link = Link->Flink) { Interfacep = CONTAINING_RECORD(Link, NAT_INTERFACE, Link); // // Traverse the interface's ticket list // KeAcquireSpinLockAtDpcLevel(&Interfacep->Lock); for (Link = Interfacep->TicketList.Flink; Link != &Interfacep->TicketList; Link = Link->Flink) { Ticketp = CONTAINING_RECORD(Link, NAT_TICKET, Link); if (NAT_TICKET_PERSISTENT(Ticketp)) { continue; } if (Ticketp->LastAccessTime >= UdpMinAccessTime) { continue; } Link = Link->Blink; NatDeleteTicket(Interfacep, Ticketp); } KeReleaseSpinLockFromDpcLevel(&Interfacep->Lock); Link = &Interfacep->Link; } KeReleaseSpinLock(&InterfaceLock, Irql); return; } // NatTimerRoutine VOID NatTriggerTimer( VOID ) { if (!InterlockedCompareExchange(&CleanupDpcPending, TRUE, FALSE)) { #if DBG DbgPrint("NatTriggerTimer: scheduling DPC\n"); #endif KeInsertQueueDpc(&CleanupDpcObject, NULL, NULL); } }