#line 1 "manyicmp.c" //================================================================================ // Microsoft Confidential // Copyright (C) Microsoft Corporation 1997 // // Author: RameshV //================================================================================ //================================================================================ // Required headers //================================================================================ #include "inc.h" #pragma hdrstop #include #include #include #include #include #include #include #include #include "adt.c" //================================================================================ // Required function, IMPORTED //================================================================================ VOID HandleIcmpResult( // Handle a processed ICMP packet. IPAddr DestAddr, // Attempted dest address BOOL Status, // Non-zero=> Dest reachable LPVOID Context // Whatever was passed to DoIcmpRe.. ); //================================================================================ // Functions EXPORTED //================================================================================ DWORD // Win32 errors DoIcmpRequest( // send icmp req. and process asynchro.. IPAddr DestAddr, // Address to send ping to LPVOID Context // the parameter to above function.. ); DWORD // Win32 errors PingInit( // Initialize all globals.. VOID ); VOID PingCleanup( // Free memory and close handles.. VOID ); //================================================================================ // Some defines //================================================================================ #define PING_TEST // Yes, we are testing PING. #define WAIT_TIME 5000 #define RCV_BUF_SIZE 0x2000 #define SEND_MESSAGE "IcmpDhcpTest" #define THREAD_KILL_TIME INFINITE #define MAX_PENDING_REQUESTS 100 #define NUM_RETRIES 3 #if DBG #define DhcpAssert(Condition) do{ \ if(!(Condition)) AssertFailed(#Condition, __FILE__, __LINE__); } \ while(0) #define ErrorPrint printf #else #define DhcpAssert(Condition) #define ErrorPrint (void) #endif //================================================================================ // Data structures NOT EXPORTED //================================================================================ // The follwing is the structure that is passed back to the callback function typedef struct st_apcContext { // struct passed to APC routine LIST_ENTRY IcmpRepliesList; // the chain of replies got is stored here LIST_ENTRY IcmpRequestsList; // The list that holds the icmp response PICMP_ECHO_REPLY Reply; // Icmp reply packet DWORD ReplySize; // The size of above buffer. IPAddr DestinationAddress; // Who are we try to ping? DWORD Status; // Did we succeed? Also retry count. LPVOID Context; // Dont know what this is goint to be } APC_CONTEXT, *PAPC_CONTEXT; // All globals are here. LIST_ENTRY IcmpRepliesList; // Here is where the IcmpReplies are spooled LIST_ENTRY IcmpRequestsList; // Here is where the CRITICAL_SECTION IcmpRepliesCritSect; // To access the replies list CRITICAL_SECTION IcmpRequestsCritSect; // To access the requests list HANDLE IcmpRepliesEvent; // Signaled each time a reply is received HANDLE IcmpRequestsEvent; // Singaled whenever a request is received HANDLE TerminateEvent; // Quit everything being done. CRITICAL_SECTION OutputCritSect; // To co-ordinate access to console output HANDLE RequestsThreadHandle; // The handle of the thread that takes requests HANDLE RepliesThreadHandle; // The handle of the thread that takes replies HANDLE IcmpHandle; // Handle to do IcmpSendEcho2 etc. PRODCONS IcmpProdConsSynchObj; // Producer/Consumer synchro.. object BOOL Terminating = FALSE; // Are we terminating? #define LOCK_REPLIES_LIST() EnterCriticalSection(&IcmpRepliesCritSect) #define LOCK_REQUESTS_LIST() EnterCriticalSection(&IcmpRequestsCritSect) #define LOCK_OUTPUT() EnterCriticalSection(&OutputCritSect) #define UNLOCK_REPLIES_LIST() LeaveCriticalSection(&IcmpRepliesCritSect) #define UNLOCK_REQUESTS_LIST() LeaveCriticalSection(&IcmpRequestsCritSect) #define UNLOCK_OUTPUT() LeaveCriticalSection(&OutputCritSect) //================================================================================ // Assertion failure routine + allocation free routines //================================================================================ VOID static _inline AssertFailed( LPSTR Condition, LPSTR File, DWORD Line ) { BYTE Buf[1000]; DWORD RetVal; sprintf(Buf, "[%s:%d] %s", File, Line, Condition); RetVal = MessageBox( NULL, // hWnd: NULL => default desktop Buf, // Text to print "Assertion Failure", // Window caption MB_OK | MB_ICONSTOP // Message type ); } LPVOID _inline DhcpAllocateMemory( DWORD Size ) { return LocalAlloc(LMEM_FIXED, Size); } VOID _inline DhcpFreeMemory( LPVOID Ptr ) { LocalFree(Ptr); } //================================================================================ // Routines //================================================================================ //-------------------------------------------------------------------------------- // The following functions are on the replies side. They handle the icmp reply // packet and take the necessary action depending on the status, etc. //-------------------------------------------------------------------------------- VOID static ApcRoutine( // This is called when ping completes IN PVOID Context, // The above structure IN PIO_STATUS_BLOCK Ignored1, // Unused param IN ULONG Ignored2 // Unused param ) { BOOL Status; PAPC_CONTEXT ApcContext = (PAPC_CONTEXT)Context; if( TRUE == Terminating ) { // Just free memory and quit? ASSERT( FALSE ); DhcpFreeMemory(ApcContext); return; } // All we have to do is add it to the Replies List and signal the event LOCK_REPLIES_LIST(); InsertTailList(&IcmpRepliesList, &ApcContext->IcmpRepliesList); UNLOCK_REPLIES_LIST(); Status = SetEvent(IcmpRepliesEvent); ASSERT( FALSE != Status ); } // The following function decides if the Destination is reachable or not. BOOL static DestReachable( // Is destination reachable? IN PAPC_CONTEXT ApcContext // this has the info of sender etc.. ) { DWORD nReplies, i; // First parse the packet. nReplies = IcmpParseReplies(ApcContext->Reply, ApcContext->ReplySize); // if no replies, then straight assume that destination is unreachable. if( 0 == nReplies ) { // If there was no reply, there is no way for us to reach this // So, we assume that the Dest is NOT reachable. // Reasons could be IP_REQ_TIMED_OUT or IP_BAD_DESTINATION etc.. return FALSE; } // Now we check each reply to see if there is anything from the same dest // address we are looking for. And if the status is success. If the status // is not success, we actually do not check anything there. Potentially, it // could be IP_DEST_PORT_UNREACHABLE, in which case, the dest machine is up, // but for some reason we tried the wrong port.. for( i = 0; i < nReplies; i ++ ) { if( ApcContext->DestinationAddress == ApcContext->Reply[i].Address ) { // hit the destination! ASSERT( IP_SUCCESS == ApcContext->Reply[i].Status ); return TRUE; } ASSERT( IP_SUCCESS != ApcContext->Reply[i].Status); } // None of the replies were successful. return FALSE; } VOID static HandleRepliesEvent( // Process all replies received VOID ) { PAPC_CONTEXT ApcContext; PLIST_ENTRY listEntry; BOOL Status; LOCK_REPLIES_LIST(); while( !IsListEmpty( &IcmpRepliesList ) ) { // retrive the first element in the list ApcContext = CONTAINING_RECORD(IcmpRepliesList.Flink, APC_CONTEXT, IcmpRepliesList); RemoveEntryList(&ApcContext->IcmpRepliesList); UNLOCK_REPLIES_LIST(); Status = DestReachable(ApcContext); if( Status || NUM_RETRIES <= ApcContext->Status ) { StartConsumer(&IcmpProdConsSynchObj); HandleIcmpResult( ApcContext->DestinationAddress, Status, ApcContext->Context ); DhcpFreeMemory(ApcContext); EndConsumer(&IcmpProdConsSynchObj); } else { // retry LOCK_REQUESTS_LIST(); InsertTailList(&IcmpRequestsList, &ApcContext->IcmpRequestsList); UNLOCK_REQUESTS_LIST(); Status = SetEvent(IcmpRequestsEvent); ASSERT( TRUE == Status ); } LOCK_REPLIES_LIST(); } UNLOCK_REPLIES_LIST(); } // This routine sleeps on a loop, and is woken up by the call back function when an ICMP // reply comes through. On waking up, this routine processes ALL ICMP replies. DWORD static // THREAD ENTRY LoopOnIcmpReplies( // Loop on all the ICMP replies. LPVOID Unused ) { DWORD Status; HANDLE WaitHandles[2]; WaitHandles[0] = TerminateEvent; WaitHandles[1] = IcmpRepliesEvent; while( TRUE ) { Status = WaitForMultipleObjects( sizeof(WaitHandles)/sizeof(WaitHandles[0]), WaitHandles, FALSE, INFINITE ); if( WAIT_OBJECT_0 == Status ) break; // Termination if( 1+WAIT_OBJECT_0 == Status ) { HandleRepliesEvent(); continue; } ASSERT( FALSE ); } return ERROR_SUCCESS; } #define AlignSizeof(X) ROUND_UP_COUNT(sizeof(X),ALIGN_WORST) DWORD // exported DoIcmpRequest( IPAddr DestAddr, LPVOID Context ) { PAPC_CONTEXT pCtxt; LPBYTE startAddress; DWORD Status; BOOL BoolStatus; // Create the context pCtxt = DhcpAllocateMemory(AlignSizeof(APC_CONTEXT) + RCV_BUF_SIZE); startAddress = (LPBYTE)pCtxt; if( NULL == pCtxt ) return GetLastError(); // Now fill the context with all that we know. pCtxt->Reply = (PICMP_ECHO_REPLY)(startAddress + AlignSizeof(APC_CONTEXT)); pCtxt->ReplySize = RCV_BUF_SIZE; pCtxt->DestinationAddress = DestAddr; pCtxt->Status = 0; pCtxt->Context = Context; // enqueue this guy. StartProducer(&IcmpProdConsSynchObj); LOCK_REQUESTS_LIST(); InsertTailList(&IcmpRequestsList, &pCtxt->IcmpRequestsList); UNLOCK_REQUESTS_LIST(); EndProducer(&IcmpProdConsSynchObj); // Signal the Requests loop. BoolStatus = SetEvent(IcmpRequestsEvent); ASSERT( TRUE == BoolStatus ); return ERROR_SUCCESS; } //-------------------------------------------------------------------------------- // The following functions handle the the end that sends ICMP echoes. //-------------------------------------------------------------------------------- VOID static HandleRequestsEvent( // Process every request for ICMP echo. VOID ) { PAPC_CONTEXT ApcContext; PLIST_ENTRY listEntry; DWORD Status; LOCK_REQUESTS_LIST(); while( !IsListEmpty( &IcmpRequestsList ) ) { // retrive the first element in the list ApcContext = CONTAINING_RECORD(IcmpRequestsList.Flink, APC_CONTEXT, IcmpRequestsList); RemoveEntryList(&ApcContext->IcmpRequestsList); UNLOCK_REQUESTS_LIST(); // Send an Icmp echo and return immediately.. ApcContext->Status ++; Status = IcmpSendEcho2( IcmpHandle, // The handle to register APC and send echo NULL, // No event ApcRoutine, // The call back routine (LPVOID)ApcContext, // The first parameter to the callback routine ApcContext->DestinationAddress, // The address being PING'ed SEND_MESSAGE, (WORD)strlen(SEND_MESSAGE), NULL, (LPVOID)ApcContext->Reply, ApcContext->ReplySize, WAIT_TIME ); if( FALSE == Status ) Status = GetLastError(); else Status = ERROR_SUCCESS; // Since we queued an APC, we expect an STATUS_PENDING. if( ERROR_SUCCESS != Status && ERROR_IO_PENDING != Status ) { ASSERT(FALSE); LOCK_OUTPUT(); ErrorPrint("IcmpSendEcho2:GetLastError: %d\n", Status); UNLOCK_OUTPUT(); } LOCK_REQUESTS_LIST(); } UNLOCK_REQUESTS_LIST(); } // This function handles the Requests.. For each one, it just sends an IcmpEcho // asynchronously and returns back immediately. When the APC routine is called, // it would queue it up on the Replies list and then it would get processed... DWORD static // THREAD ENTRY LoopOnIcmpRequests( // Process pending requests for echo LPVOID Unused ) { DWORD Status; HANDLE WaitHandles[2]; WaitHandles[0] = TerminateEvent; WaitHandles[1] = IcmpRequestsEvent; while( TRUE ) { Status = WaitForMultipleObjectsEx( sizeof(WaitHandles)/sizeof(WaitHandles[0]), // # of handles WaitHandles, // array of handles FALSE, // any one of them set INFINITE, // wait forever TRUE // allow APC's ); if( WAIT_OBJECT_0 == Status ) break; // Termination if( WAIT_IO_COMPLETION == Status) continue; if( 1+WAIT_OBJECT_0 == Status ) { HandleRequestsEvent(); continue; } ASSERT(FALSE); } return ERROR_SUCCESS; } //-------------------------------------------------------------------------------- // Initialization, Cleanup routines. //-------------------------------------------------------------------------------- DWORD // exported PingInit( VOID ) { DWORD ThreadId, Status; // Initialize all data vars. IcmpRepliesEvent = IcmpRequestsEvent = TerminateEvent = NULL; RepliesThreadHandle = RequestsThreadHandle = NULL; IcmpHandle = NULL; // Open IcmpHandle.. IcmpHandle = IcmpCreateFile(); if( NULL == IcmpHandle ) return GetLastError(); // Initialize Lists. InitializeListHead(&IcmpRepliesList); InitializeListHead(&IcmpRequestsList); // Initialize Critical Sections. InitializeCriticalSection(&IcmpRepliesCritSect); InitializeCriticalSection(&IcmpRequestsCritSect); InitializeCriticalSection(&OutputCritSect); // Create Events, IcmpRepliesEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if( NULL == IcmpRepliesEvent ) return GetLastError(); IcmpRequestsEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if( NULL == IcmpRequestsEvent ) return GetLastError(); TerminateEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); if( NULL == TerminateEvent ) return GetLastError(); // Create producer-consumer synchronization object Status = InitializeProducerConsumer( &IcmpProdConsSynchObj, MAX_PENDING_REQUESTS, MAX_PENDING_REQUESTS ); if( ERROR_SUCCESS != Status ) return Status; // Create Threads RepliesThreadHandle = CreateThread( (LPSECURITY_ATTRIBUTES) NULL, // No security information 0, // Stack size = same as default primary thread LoopOnIcmpReplies, // The function to call NULL, // No paramter needs to be passed to this function 0, // Flags: just start this thread right away &ThreadId // The return ThreadId value. ); if( NULL == RepliesThreadHandle ) return GetLastError(); RequestsThreadHandle = CreateThread( NULL, // No security information 0, // Stack size = same as default primary thread LoopOnIcmpRequests, // The function to call NULL, // No paramter needs to be passed to this function 0, // Flags: just start this thread right away &ThreadId // The return ThreadId value. ); if( NULL == RequestsThreadHandle ) return GetLastError(); return ERROR_SUCCESS; } VOID // exported PingCleanup( VOID ) { DWORD Status; BOOL BoolStatus; PAPC_CONTEXT ApcContext; PLIST_ENTRY listEntry; // Kill the replies and reqeusts threads after waiting for a while. // Kill the Replies and Requests ThreadHandle 's. if( NULL != RepliesThreadHandle || NULL != RequestsThreadHandle ) { // ASSERT ( NULL != TerminateEvent ) Terminating = TRUE; SetEvent(TerminateEvent); if( NULL != RepliesThreadHandle ) { Status = WaitForSingleObject( RepliesThreadHandle, THREAD_KILL_TIME ); if( WAIT_OBJECT_0 != Status ) { // did not succeed in stopping the thread.. BoolStatus = TerminateThread( RepliesThreadHandle, 0xFF ); ASSERT(BoolStatus); } CloseHandle(RepliesThreadHandle); } if( NULL != RequestsThreadHandle ) { Status = WaitForSingleObject( RequestsThreadHandle, THREAD_KILL_TIME ); if( WAIT_OBJECT_0 != Status ) { // did not succeed in stopping the thread.. BoolStatus = TerminateThread( RequestsThreadHandle, 0xFF ); ASSERT(BoolStatus); } CloseHandle(RequestsThreadHandle); } } // Close Event handles. CloseHandle(IcmpRepliesEvent); CloseHandle(IcmpRequestsEvent); CloseHandle(TerminateEvent); // Destroy producer consumer synchronization object DestroyProducerConsumer(&IcmpProdConsSynchObj); // Freeup all elements of lists.. while( !IsListEmpty( &IcmpRepliesList ) ) { // retrive the first element in the list ApcContext = CONTAINING_RECORD(IcmpRepliesList.Flink, APC_CONTEXT, IcmpRepliesList); RemoveEntryList(&ApcContext->IcmpRepliesList); DhcpFreeMemory(ApcContext); } while( !IsListEmpty( &IcmpRequestsList ) ) { // retrive the first element in the list ApcContext = CONTAINING_RECORD(IcmpRequestsList.Flink, APC_CONTEXT, IcmpRequestsList); RemoveEntryList(&ApcContext->IcmpRequestsList); DhcpFreeMemory(ApcContext); } // Close Icmp handle CloseHandle(IcmpHandle); // Destroy critical sections DeleteCriticalSection(&IcmpRepliesCritSect); DeleteCriticalSection(&IcmpRequestsCritSect); DeleteCriticalSection(&OutputCritSect); } #ifdef PING_TEST //-------------------------------------------------------------------------------- // Test module. This exercises the above functions. //-------------------------------------------------------------------------------- DWORD SA, EA; DWORD TestPing( LPSTR StartAddrString, LPSTR EndAddrString ) { IPAddr i, StartAddr, EndAddr; DWORD Status; BOOL BoolStatus; HANDLE ThreadHandle; StartAddr = SA = inet_addr(StartAddrString); EndAddr = EA = inet_addr(EndAddrString); Status = PingInit(); if( ERROR_SUCCESS != Status ) return Status; for( i = htonl(StartAddr); i <= htonl(EndAddr); i ++ ) { Status = DoIcmpRequest(ntohl(i), NULL); ASSERT( ERROR_SUCCESS == Status ); } LOCK_OUTPUT(); printf("Done\n"); UNLOCK_OUTPUT(); // Sleep for a while and then signal termination... Sleep(30000); // Give the threads time to die? PingCleanup(); return ERROR_SUCCESS; } //-------------------------------------------------------------------------------- // Main function. Just does the test routine. //-------------------------------------------------------------------------------- VOID _cdecl main( int argc, char *argv[] ) { DWORD Status; if( argc != 3 ) { fprintf(stderr, "\nUsage: %s start-ip-addr end-ip-addr\n", argv[0]); exit(1); } Status = TestPing(argv[1], argv[2]); if( ERROR_SUCCESS != Status ) { fprintf(stderr, "Error: %d\n", Status); } } #endif PING_TEST //-------------------------------------------------------------------------------- // End of file. //-------------------------------------------------------------------------------- // This will really be defined elsewhere.. until then. VOID HandleIcmpResult( // Handle a processed ICMP packet. IPAddr DestAddr, // Attempted dest address BOOL Status, // Non-zero=> Dest reachable LPVOID Context // Whatever was passed to DoIcmpRe.. ) { LOCK_OUTPUT(); printf("Ping[%s] does %s exist\n", inet_ntoa(*(struct in_addr *)&DestAddr), Status? "" : "not" ); UNLOCK_OUTPUT(); }