windows-nt/Source/XPSP1/NT/net/tcpip/apis/iphlpapi/icmptest/icmptest.c

666 lines
22 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
#line 1 "manyicmp.c"
//================================================================================
// Microsoft Confidential
// Copyright (C) Microsoft Corporation 1997
//
// Author: RameshV
//================================================================================
//================================================================================
// Required headers
//================================================================================
#include "inc.h"
#pragma hdrstop
#include <ipexport.h>
#include <icmpif.h>
#include <icmpapi.h>
#include <winsock.h>
#include <stdio.h>
#include <stdlib.h>
#include <align.h>
#include <adt.h>
#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();
}