mirror of
https://github.com/reactos/reactos.git
synced 2024-10-31 03:48:17 +00:00
f355097ad2
svn path=/trunk/; revision=2445
630 lines
17 KiB
C
630 lines
17 KiB
C
/* $Id: ping.c,v 1.1 2001/12/30 04:09:38 sedwards Exp $
|
|
*
|
|
* COPYRIGHT: See COPYING in the top level directory
|
|
* PROJECT: ReactOS ping utility
|
|
* FILE: apps/net/ping/ping.c
|
|
* PURPOSE: Network test utility
|
|
* PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
|
|
* REVISIONS:
|
|
* CSH 01/09/2000 Created
|
|
*/
|
|
#include <winsock2.h>
|
|
#include <tchar.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#ifndef _MSC_VER
|
|
|
|
//#define DBG
|
|
|
|
/* FIXME: Where should this be? */
|
|
#define CopyMemory(Destination, Source, Length) memcpy(Destination, Source, Length);
|
|
|
|
/* Should be in the header files somewhere (exported by ntdll.dll) */
|
|
long atol(const char *str);
|
|
|
|
#ifndef __int64
|
|
typedef long long __int64;
|
|
#endif
|
|
|
|
char * _i64toa(__int64 value, char *string, int radix);
|
|
|
|
#endif
|
|
|
|
|
|
/* General ICMP constants */
|
|
#define ICMP_MINSIZE 8 /* Minimum ICMP packet size */
|
|
#define ICMP_MAXSIZE 65535 /* Maximum ICMP packet size */
|
|
|
|
/* ICMP message types */
|
|
#define ICMPMSG_ECHOREQUEST 8 /* ICMP ECHO request message */
|
|
#define ICMPMSG_ECHOREPLY 0 /* ICMP ECHO reply message */
|
|
|
|
#pragma pack(4)
|
|
|
|
/* IPv4 header structure */
|
|
typedef struct _IPv4_HEADER {
|
|
unsigned char IHL:4;
|
|
unsigned char Version:4;
|
|
unsigned char TOS;
|
|
unsigned short Length;
|
|
unsigned short Id;
|
|
unsigned short FragFlags;
|
|
unsigned char TTL;
|
|
unsigned char Protocol;
|
|
unsigned short Checksum;
|
|
unsigned int SrcAddress;
|
|
unsigned int DstAddress;
|
|
} IPv4_HEADER, *PIPv4_HEADER;
|
|
|
|
/* ICMP echo request/reply header structure */
|
|
typedef struct _ICMP_HEADER {
|
|
unsigned char Type;
|
|
unsigned char Code;
|
|
unsigned short Checksum;
|
|
unsigned short Id;
|
|
unsigned short SeqNum;
|
|
} ICMP_HEADER, *PICMP_HEADER;
|
|
|
|
typedef struct _ICMP_ECHO_PACKET {
|
|
ICMP_HEADER Icmp;
|
|
LARGE_INTEGER Timestamp;
|
|
} ICMP_ECHO_PACKET, *PICMP_ECHO_PACKET;
|
|
|
|
#pragma pack(1)
|
|
|
|
BOOL InvalidOption;
|
|
BOOL NeverStop;
|
|
BOOL ResolveAddresses;
|
|
UINT PingCount;
|
|
UINT DataSize; /* ICMP echo request data size */
|
|
BOOL DontFragment;
|
|
ULONG TTLValue;
|
|
ULONG TOSValue;
|
|
ULONG Timeout;
|
|
CHAR TargetName[256];
|
|
SOCKET IcmpSock;
|
|
SOCKADDR_IN Target;
|
|
LPSTR TargetIP;
|
|
FD_SET Fds;
|
|
TIMEVAL Timeval;
|
|
UINT CurrentSeqNum;
|
|
UINT SentCount;
|
|
UINT LostCount;
|
|
BOOL MinRTTSet;
|
|
LARGE_INTEGER MinRTT; /* Minimum round trip time in microseconds */
|
|
LARGE_INTEGER MaxRTT;
|
|
LARGE_INTEGER SumRTT;
|
|
LARGE_INTEGER AvgRTT;
|
|
LARGE_INTEGER TicksPerMs; /* Ticks per millisecond */
|
|
LARGE_INTEGER TicksPerUs; /* Ticks per microsecond */
|
|
BOOL UsePerformanceCounter;
|
|
|
|
|
|
/* Display the contents of a buffer */
|
|
VOID DisplayBuffer(
|
|
PVOID Buffer,
|
|
DWORD Size)
|
|
{
|
|
UINT i;
|
|
PCHAR p;
|
|
|
|
printf("Buffer (0x%X) Size (0x%X).\n", Buffer, Size);
|
|
|
|
p = (PCHAR)Buffer;
|
|
for (i = 0; i < Size; i++) {
|
|
if (i % 16 == 0) {
|
|
printf("\n");
|
|
}
|
|
printf("%02X ", (p[i]) & 0xFF);
|
|
}
|
|
}
|
|
|
|
/* Display usage information on screen */
|
|
VOID Usage(VOID)
|
|
{
|
|
printf("\nUsage: ping [-t] [-n count] [-l size] [-w timeout] destination-host\n\n");
|
|
printf("Options:\n");
|
|
printf(" -t Ping the specified host until stopped.\n");
|
|
printf(" To stop - type Control-C.\n");
|
|
printf(" -n count Number of echo requests to send.\n");
|
|
printf(" -l size Send buffer size.\n");
|
|
printf(" -w timeout Timeout in milliseconds to wait for each reply.\n\n");
|
|
}
|
|
|
|
/* Reset configuration to default values */
|
|
VOID Reset(VOID)
|
|
{
|
|
LARGE_INTEGER PerformanceCounterFrequency;
|
|
|
|
NeverStop = FALSE;
|
|
ResolveAddresses = FALSE;
|
|
PingCount = 4;
|
|
DataSize = 32;
|
|
DontFragment = FALSE;
|
|
TTLValue = 128;
|
|
TOSValue = 0;
|
|
Timeout = 1000;
|
|
UsePerformanceCounter = QueryPerformanceFrequency(&PerformanceCounterFrequency);
|
|
|
|
if (UsePerformanceCounter) {
|
|
/* Performance counters may return incorrect results on some multiprocessor
|
|
platforms so we restrict execution on the first processor. This may fail
|
|
on Windows NT so we fall back to GetCurrentTick() for timing */
|
|
if (SetThreadAffinityMask (GetCurrentThread(), 1) == 0) {
|
|
UsePerformanceCounter = FALSE;
|
|
}
|
|
|
|
/* Convert frequency to ticks per millisecond */
|
|
TicksPerMs.QuadPart = PerformanceCounterFrequency.QuadPart / 1000;
|
|
/* And to ticks per microsecond */
|
|
TicksPerUs.QuadPart = PerformanceCounterFrequency.QuadPart / 1000000;
|
|
}
|
|
if (!UsePerformanceCounter) {
|
|
/* 1 tick per millisecond for GetCurrentTick() */
|
|
TicksPerMs.QuadPart = 1;
|
|
/* GetCurrentTick() cannot handle microseconds */
|
|
TicksPerUs.QuadPart = 1;
|
|
}
|
|
}
|
|
|
|
/* Return ULONG in a string */
|
|
ULONG GetULONG(LPSTR String)
|
|
{
|
|
UINT i, Length;
|
|
ULONG Value;
|
|
|
|
i = 0;
|
|
Length = strlen(String);
|
|
while ((i < Length) && ((String[i] < '0') || (String[i] > '9'))) i++;
|
|
if ((i >= Length) || ((String[i] < '0') || (String[i] > '9'))) {
|
|
InvalidOption = TRUE;
|
|
return 0;
|
|
}
|
|
Value = (ULONG)atol(&String[i]);
|
|
|
|
return Value;
|
|
}
|
|
|
|
/* Return ULONG in a string. Try next paramter if not successful */
|
|
ULONG GetULONG2(LPSTR String1, LPSTR String2, PINT i)
|
|
{
|
|
ULONG Value;
|
|
|
|
Value = GetULONG(String1);
|
|
if (InvalidOption) {
|
|
InvalidOption = FALSE;
|
|
if (String2[0] != '-') {
|
|
Value = GetULONG(String2);
|
|
if (!InvalidOption)
|
|
*i += 1;
|
|
}
|
|
}
|
|
|
|
return Value;
|
|
}
|
|
|
|
/* Parse command line parameters */
|
|
BOOL ParseCmdline(int argc, char* argv[])
|
|
{
|
|
INT i;
|
|
BOOL ShowUsage;
|
|
BOOL FoundTarget;
|
|
#if 1
|
|
lstrcpy(TargetName, "127.0.0.1");
|
|
PingCount = 1;
|
|
return TRUE;
|
|
#endif
|
|
if (argc < 2) {
|
|
ShowUsage = TRUE;
|
|
} else {
|
|
ShowUsage = FALSE;
|
|
}
|
|
FoundTarget = FALSE;
|
|
InvalidOption = FALSE;
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
if (argv[i][0] == '-') {
|
|
switch (argv[i][1]) {
|
|
case 't': NeverStop = TRUE; break;
|
|
case 'a': ResolveAddresses = TRUE; break;
|
|
case 'n': PingCount = GetULONG2(&argv[i][2], argv[i + 1], &i); break;
|
|
case 'l':
|
|
DataSize = GetULONG2(&argv[i][2], argv[i + 1], &i);
|
|
if ((DataSize < 0) || (DataSize > ICMP_MAXSIZE - sizeof(ICMP_ECHO_PACKET))) {
|
|
printf("Bad value for option -l, valid range is from 0 to %d.\n",
|
|
ICMP_MAXSIZE - sizeof(ICMP_ECHO_PACKET));
|
|
return FALSE;
|
|
}
|
|
break;
|
|
case 'f': DontFragment = TRUE; break;
|
|
case 'i': TTLValue = GetULONG2(&argv[i][2], argv[i + 1], &i); break;
|
|
case 'v': TOSValue = GetULONG2(&argv[i][2], argv[i + 1], &i); break;
|
|
case 'w': Timeout = GetULONG2(&argv[i][2], argv[i + 1], &i); break;
|
|
default:
|
|
printf("Bad option %s.\n", argv[i]);
|
|
Usage();
|
|
return FALSE;
|
|
}
|
|
if (InvalidOption) {
|
|
printf("Bad option format %s.\n", argv[i]);
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
if (FoundTarget) {
|
|
printf("Bad parameter %s.\n", argv[i]);
|
|
return FALSE;
|
|
} else {
|
|
lstrcpy(TargetName, argv[i]);
|
|
FoundTarget = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((!ShowUsage) && (!FoundTarget)) {
|
|
printf("Name or IP address of destination host must be specified.\n");
|
|
return FALSE;
|
|
}
|
|
|
|
if (ShowUsage) {
|
|
Usage();
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* Calculate checksum of data */
|
|
WORD Checksum(PUSHORT data, UINT size)
|
|
{
|
|
ULONG sum = 0;
|
|
|
|
while (size > 1) {
|
|
sum += *data++;
|
|
size -= sizeof(USHORT);
|
|
}
|
|
|
|
if (size)
|
|
sum += *(UCHAR*)data;
|
|
|
|
sum = (sum >> 16) + (sum & 0xFFFF);
|
|
sum += (sum >> 16);
|
|
|
|
return (USHORT)(~sum);
|
|
}
|
|
|
|
/* Prepare to ping target */
|
|
BOOL Setup(VOID)
|
|
{
|
|
WORD wVersionRequested;
|
|
WSADATA WsaData;
|
|
INT Status;
|
|
ULONG Addr;
|
|
PHOSTENT phe;
|
|
|
|
wVersionRequested = MAKEWORD(2, 2);
|
|
|
|
Status = WSAStartup(wVersionRequested, &WsaData);
|
|
if (Status != 0) {
|
|
printf("Could not initialize winsock dll.\n");
|
|
return FALSE;
|
|
}
|
|
|
|
IcmpSock = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, 0);
|
|
if (IcmpSock == INVALID_SOCKET) {
|
|
printf("Could not create socket (#%d).\n", WSAGetLastError());
|
|
return FALSE;
|
|
}
|
|
|
|
ZeroMemory(&Target, sizeof(Target));
|
|
phe = NULL;
|
|
Addr = inet_addr(TargetName);
|
|
if (Addr == INADDR_NONE) {
|
|
phe = gethostbyname(TargetName);
|
|
if (phe == NULL) {
|
|
printf("Unknown host %s.\n", TargetName);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (phe != NULL) {
|
|
CopyMemory(&Target.sin_addr, phe->h_addr_list, phe->h_length);
|
|
} else {
|
|
Target.sin_addr.s_addr = Addr;
|
|
}
|
|
|
|
if (phe != NULL) {
|
|
Target.sin_family = phe->h_addrtype;
|
|
} else {
|
|
Target.sin_family = AF_INET;
|
|
}
|
|
|
|
TargetIP = inet_ntoa(Target.sin_addr);
|
|
CurrentSeqNum = 0;
|
|
SentCount = 0;
|
|
LostCount = 0;
|
|
MinRTT.QuadPart = 0;
|
|
MaxRTT.QuadPart = 0;
|
|
SumRTT.QuadPart = 0;
|
|
MinRTTSet = FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
/* Close socket */
|
|
VOID Cleanup(VOID)
|
|
{
|
|
if (IcmpSock != INVALID_SOCKET)
|
|
closesocket(IcmpSock);
|
|
|
|
WSACleanup();
|
|
}
|
|
|
|
VOID QueryTime(PLARGE_INTEGER Time)
|
|
{
|
|
if (UsePerformanceCounter) {
|
|
if (QueryPerformanceCounter(Time) == 0) {
|
|
/* This should not happen, but we fall
|
|
back to GetCurrentTick() if it does */
|
|
Time->u.LowPart = (ULONG)GetTickCount();
|
|
Time->u.HighPart = 0;
|
|
|
|
/* 1 tick per millisecond for GetCurrentTick() */
|
|
TicksPerMs.QuadPart = 1;
|
|
/* GetCurrentTick() cannot handle microseconds */
|
|
TicksPerUs.QuadPart = 1;
|
|
|
|
UsePerformanceCounter = FALSE;
|
|
}
|
|
} else {
|
|
Time->u.LowPart = (ULONG)GetTickCount();
|
|
Time->u.HighPart = 0;
|
|
}
|
|
}
|
|
|
|
VOID TimeToMsString(LPSTR String, LARGE_INTEGER Time)
|
|
{
|
|
UINT i, Length;
|
|
CHAR Convstr[40];
|
|
LARGE_INTEGER LargeTime;
|
|
|
|
LargeTime.QuadPart = Time.QuadPart / TicksPerMs.QuadPart;
|
|
_i64toa(LargeTime.QuadPart, Convstr, 10);
|
|
strcpy(String, Convstr);
|
|
strcat(String, ",");
|
|
|
|
LargeTime.QuadPart = (Time.QuadPart % TicksPerMs.QuadPart) / TicksPerUs.QuadPart;
|
|
_i64toa(LargeTime.QuadPart, Convstr, 10);
|
|
Length = strlen(Convstr);
|
|
if (Length < 4) {
|
|
for (i = 0; i < 4 - Length; i++)
|
|
strcat(String, "0");
|
|
}
|
|
|
|
strcat(String, Convstr);
|
|
strcat(String, "ms");
|
|
}
|
|
|
|
/* Locate the ICMP data and print it. Returns TRUE if the packet was good,
|
|
FALSE if not */
|
|
BOOL DecodeResponse(PCHAR buffer, UINT size, PSOCKADDR_IN from)
|
|
{
|
|
PIPv4_HEADER IpHeader;
|
|
PICMP_ECHO_PACKET Icmp;
|
|
UINT IphLength;
|
|
CHAR Time[100];
|
|
LARGE_INTEGER RelativeTime;
|
|
LARGE_INTEGER LargeTime;
|
|
|
|
IpHeader = (PIPv4_HEADER)buffer;
|
|
|
|
IphLength = IpHeader->IHL * 4;
|
|
|
|
if (size < IphLength + ICMP_MINSIZE) {
|
|
#ifdef DBG
|
|
printf("Bad size (0x%X < 0x%X)\n", size, IphLength + ICMP_MINSIZE);
|
|
#endif DBG
|
|
return FALSE;
|
|
}
|
|
|
|
Icmp = (PICMP_ECHO_PACKET)(buffer + IphLength);
|
|
|
|
if (Icmp->Icmp.Type != ICMPMSG_ECHOREPLY) {
|
|
#ifdef DBG
|
|
printf("Bad ICMP type (0x%X should be 0x%X)\n", Icmp->Icmp.Type, ICMPMSG_ECHOREPLY);
|
|
#endif DBG
|
|
return FALSE;
|
|
}
|
|
|
|
if (Icmp->Icmp.Id != (USHORT)GetCurrentProcessId()) {
|
|
#ifdef DBG
|
|
printf("Bad ICMP id (0x%X should be 0x%X)\n", Icmp->Icmp.Id, (USHORT)GetCurrentProcessId());
|
|
#endif DBG
|
|
return FALSE;
|
|
}
|
|
|
|
QueryTime(&LargeTime);
|
|
|
|
RelativeTime.QuadPart = (LargeTime.QuadPart - Icmp->Timestamp.QuadPart);
|
|
|
|
TimeToMsString(Time, RelativeTime);
|
|
|
|
printf("Reply from %s: bytes=%d time=%s TTL=%d\n", inet_ntoa(from->sin_addr),
|
|
size - IphLength - sizeof(ICMP_ECHO_PACKET), Time, IpHeader->TTL);
|
|
if (RelativeTime.QuadPart < MinRTT.QuadPart) {
|
|
MinRTT.QuadPart = RelativeTime.QuadPart;
|
|
MinRTTSet = TRUE;
|
|
}
|
|
if (RelativeTime.QuadPart > MaxRTT.QuadPart)
|
|
MaxRTT.QuadPart = RelativeTime.QuadPart;
|
|
SumRTT.QuadPart += RelativeTime.QuadPart;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Send and receive one ping */
|
|
BOOL Ping(VOID)
|
|
{
|
|
INT Status;
|
|
SOCKADDR From;
|
|
UINT Length;
|
|
PVOID Buffer;
|
|
UINT Size;
|
|
PICMP_ECHO_PACKET Packet;
|
|
|
|
/* Account for extra space for IP header when packet is received */
|
|
Size = DataSize + 128;
|
|
Buffer = GlobalAlloc(0, Size);
|
|
if (!Buffer) {
|
|
printf("Not enough free resources available.\n");
|
|
return FALSE;
|
|
}
|
|
|
|
ZeroMemory(Buffer, Size);
|
|
Packet = (PICMP_ECHO_PACKET)Buffer;
|
|
|
|
/* Assemble ICMP echo request packet */
|
|
Packet->Icmp.Type = ICMPMSG_ECHOREQUEST;
|
|
Packet->Icmp.Code = 0;
|
|
Packet->Icmp.Id = (USHORT)GetCurrentProcessId();
|
|
Packet->Icmp.SeqNum = (USHORT)CurrentSeqNum;
|
|
Packet->Icmp.Checksum = 0;
|
|
|
|
/* Timestamp is part of data area */
|
|
QueryTime(&Packet->Timestamp);
|
|
|
|
CopyMemory(Buffer, &Packet->Icmp, sizeof(ICMP_ECHO_PACKET) + DataSize);
|
|
/* Calculate checksum for ICMP header and data area */
|
|
Packet->Icmp.Checksum = Checksum((PUSHORT)&Packet->Icmp, sizeof(ICMP_ECHO_PACKET) + DataSize);
|
|
|
|
CurrentSeqNum++;
|
|
|
|
/* Send ICMP echo request */
|
|
|
|
FD_ZERO(&Fds);
|
|
FD_SET(IcmpSock, &Fds);
|
|
Timeval.tv_sec = Timeout / 1000;
|
|
Timeval.tv_usec = Timeout % 1000;
|
|
Status = select(0, NULL, &Fds, NULL, &Timeval);
|
|
if ((Status != SOCKET_ERROR) && (Status != 0)) {
|
|
|
|
#ifdef DBG
|
|
printf("Sending packet\n");
|
|
DisplayBuffer(Buffer, sizeof(ICMP_ECHO_PACKET) + DataSize);
|
|
printf("\n");
|
|
#endif DBG
|
|
|
|
Status = sendto(IcmpSock, Buffer, sizeof(ICMP_ECHO_PACKET) + DataSize,
|
|
0, (SOCKADDR*)&Target, sizeof(Target));
|
|
SentCount++;
|
|
}
|
|
if (Status == SOCKET_ERROR) {
|
|
if (WSAGetLastError() == WSAEHOSTUNREACH) {
|
|
printf("Destination host unreachable.\n");
|
|
} else {
|
|
printf("Could not transmit data (%d).\n", WSAGetLastError());
|
|
}
|
|
GlobalFree(Buffer);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Expect to receive ICMP echo reply */
|
|
FD_ZERO(&Fds);
|
|
FD_SET(IcmpSock, &Fds);
|
|
Timeval.tv_sec = Timeout / 1000;
|
|
Timeval.tv_usec = Timeout % 1000;
|
|
|
|
Status = select(0, &Fds, NULL, NULL, &Timeval);
|
|
if ((Status != SOCKET_ERROR) && (Status != 0)) {
|
|
Length = sizeof(From);
|
|
Status = recvfrom(IcmpSock, Buffer, Size, 0, &From, &Length);
|
|
|
|
#ifdef DBG
|
|
printf("Received packet\n");
|
|
DisplayBuffer(Buffer, Status);
|
|
printf("\n");
|
|
#endif DBG
|
|
}
|
|
if (Status == SOCKET_ERROR) {
|
|
if (WSAGetLastError() != WSAETIMEDOUT) {
|
|
printf("Could not receive data (%d).\n", WSAGetLastError());
|
|
GlobalFree(Buffer);
|
|
return FALSE;
|
|
}
|
|
Status = 0;
|
|
}
|
|
|
|
if (Status == 0) {
|
|
printf("Request timed out.\n");
|
|
LostCount++;
|
|
GlobalFree(Buffer);
|
|
return TRUE;
|
|
}
|
|
|
|
if (!DecodeResponse(Buffer, Status, (PSOCKADDR_IN)&From)) {
|
|
/* FIXME: Wait again as it could be another ICMP message type */
|
|
printf("Request timed out (incomplete datagram received).\n");
|
|
LostCount++;
|
|
}
|
|
|
|
GlobalFree(Buffer);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* Program entry point */
|
|
int main(int argc, char* argv[])
|
|
{
|
|
UINT Count;
|
|
CHAR MinTime[20];
|
|
CHAR MaxTime[20];
|
|
CHAR AvgTime[20];
|
|
|
|
Reset();
|
|
|
|
if ((ParseCmdline(argc, argv)) && (Setup())) {
|
|
|
|
printf("\nPinging %s [%s] with %d bytes of data:\n\n",
|
|
TargetName, TargetIP, DataSize);
|
|
|
|
Count = 0;
|
|
while ((NeverStop) || (Count < PingCount)) {
|
|
Ping();
|
|
Sleep(Timeout);
|
|
Count++;
|
|
};
|
|
|
|
Cleanup();
|
|
|
|
/* Calculate avarage round trip time */
|
|
if ((SentCount - LostCount) > 0) {
|
|
AvgRTT.QuadPart = SumRTT.QuadPart / (SentCount - LostCount);
|
|
} else {
|
|
AvgRTT.QuadPart = 0;
|
|
}
|
|
|
|
/* Calculate loss percent */
|
|
if (LostCount > 0) {
|
|
Count = (SentCount * 100) / LostCount;
|
|
} else {
|
|
Count = 0;
|
|
}
|
|
|
|
if (!MinRTTSet)
|
|
MinRTT = MaxRTT;
|
|
|
|
TimeToMsString(MinTime, MinRTT);
|
|
TimeToMsString(MaxTime, MaxRTT);
|
|
TimeToMsString(AvgTime, AvgRTT);
|
|
|
|
/* Print statistics */
|
|
printf("\nPing statistics for %s:\n", TargetIP);
|
|
printf(" Packets: Sent = %d, Received = %d, Lost = %d (%d%% loss),\n",
|
|
SentCount, SentCount - LostCount, LostCount, Count);
|
|
printf("Approximate round trip times in milli-seconds:\n");
|
|
printf(" Minimum = %s, Maximum = %s, Average = %s\n",
|
|
MinTime, MaxTime, AvgTime);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* EOF */
|