[PING]
[reactos.git] / reactos / base / applications / network / ping / ping.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS ping utility
4 * FILE: base/applications/network/ping/ping.c
5 * PURPOSE: Network test utility
6 * PROGRAMMERS:
7 */
8
9 #include <winsock2.h>
10 #include <ws2tcpip.h>
11 #include <tchar.h>
12 #include <stdarg.h>
13 #include <string.h>
14 #include <stdio.h>
15 #include "resource.h"
16
17 #define NDEBUG
18
19 /* General ICMP constants */
20 #define ICMP_MINSIZE 8 /* Minimum ICMP packet size */
21 #define ICMP_MAXSIZE 65535 /* Maximum ICMP packet size */
22
23 /* ICMP message types */
24 #define ICMPMSG_ECHOREQUEST 8 /* ICMP ECHO request message */
25 #define ICMPMSG_ECHOREPLY 0 /* ICMP ECHO reply message */
26
27 #pragma pack(4)
28
29 /* IPv4 header structure */
30 typedef struct _IPv4_HEADER
31 {
32 unsigned char IHL:4;
33 unsigned char Version:4;
34 unsigned char TOS;
35 unsigned short Length;
36 unsigned short Id;
37 unsigned short FragFlags;
38 unsigned char TTL;
39 unsigned char Protocol;
40 unsigned short Checksum;
41 unsigned int SrcAddress;
42 unsigned int DstAddress;
43 } IPv4_HEADER, *PIPv4_HEADER;
44
45 /* ICMP echo request/reply header structure */
46 typedef struct _ICMP_HEADER
47 {
48 unsigned char Type;
49 unsigned char Code;
50 unsigned short Checksum;
51 unsigned short Id;
52 unsigned short SeqNum;
53 } ICMP_HEADER, *PICMP_HEADER;
54
55 typedef struct _ICMP_ECHO_PACKET
56 {
57 ICMP_HEADER Icmp;
58 } ICMP_ECHO_PACKET, *PICMP_ECHO_PACKET;
59
60 #pragma pack(1)
61
62 BOOL InvalidOption;
63 BOOL NeverStop;
64 BOOL ResolveAddresses;
65 UINT PingCount;
66 UINT DataSize; /* ICMP echo request data size */
67 BOOL DontFragment;
68 ULONG TTLValue;
69 ULONG TOSValue;
70 ULONG Timeout;
71 WCHAR TargetName[256];
72 SOCKET IcmpSock;
73 SOCKADDR_IN Target;
74 WCHAR TargetIP[16];
75 FD_SET Fds;
76 TIMEVAL Timeval;
77 UINT CurrentSeqNum;
78 UINT SentCount;
79 UINT LostCount;
80 BOOL MinRTTSet;
81 LARGE_INTEGER MinRTT; /* Minimum round trip time in microseconds */
82 LARGE_INTEGER MaxRTT;
83 LARGE_INTEGER SumRTT;
84 LARGE_INTEGER AvgRTT;
85 LARGE_INTEGER TicksPerMs; /* Ticks per millisecond */
86 LARGE_INTEGER TicksPerUs; /* Ticks per microsecond */
87 LARGE_INTEGER SentTime;
88 BOOL UsePerformanceCounter;
89 HANDLE hStdOutput;
90
91 #ifndef NDEBUG
92 /* Display the contents of a buffer */
93 static VOID DisplayBuffer(
94 PVOID Buffer,
95 DWORD Size)
96 {
97 UINT i;
98 PCHAR p;
99
100 printf("Buffer (0x%p) Size (0x%lX).\n", Buffer, Size);
101
102 p = (PCHAR)Buffer;
103 for (i = 0; i < Size; i++)
104 {
105 if (i % 16 == 0)
106 printf("\n");
107 printf("%02X ", (p[i]) & 0xFF);
108 }
109 }
110 #endif /* !NDEBUG */
111
112 LPWSTR
113 MyLoadString(UINT uID)
114 {
115 HRSRC hres;
116 HGLOBAL hResData;
117 LPWSTR pwsz;
118
119 hres = FindResourceW(NULL, MAKEINTRESOURCEW((LOWORD(uID) >> 4) + 1), RT_STRING);
120 if (!hres) return NULL;
121
122 hResData = LoadResource(NULL, hres);
123 if (!hResData) return NULL;
124
125 pwsz = LockResource(hResData);
126 if (!pwsz) return NULL;
127 return pwsz + 1;
128 }
129
130 void FormatOutput(UINT uID, ...)
131 {
132 va_list valist;
133
134 WCHAR Buf[1024];
135 LPWSTR pBuf = Buf;
136 LPWSTR Format;
137 DWORD written;
138 UINT DataLength;
139
140 va_start(valist, uID);
141
142 Format = MyLoadString(uID);
143 if (!Format) return;
144
145 DataLength = FormatMessage(FORMAT_MESSAGE_FROM_STRING, Format, 0, 0, Buf,\
146 sizeof(Buf) / sizeof(WCHAR), &valist);
147
148 if(!DataLength)
149 {
150 if(GetLastError() != ERROR_INSUFFICIENT_BUFFER)
151 return;
152
153 DataLength = FormatMessage(FORMAT_MESSAGE_FROM_STRING |\
154 FORMAT_MESSAGE_ALLOCATE_BUFFER,\
155 Format, 0, 0, (LPWSTR)&pBuf, 0, &valist);
156
157 if(!DataLength)
158 return;
159 }
160
161 WriteConsole(hStdOutput, pBuf, DataLength, &written, NULL);
162
163 if(pBuf != Buf)
164 LocalFree(pBuf);
165 }
166
167 /* Display usage information on screen */
168 static VOID Usage(VOID)
169 {
170 FormatOutput(IDS_USAGE);
171 }
172
173 /* Reset configuration to default values */
174 static VOID Reset(VOID)
175 {
176 LARGE_INTEGER PerformanceCounterFrequency;
177
178 NeverStop = FALSE;
179 ResolveAddresses = FALSE;
180 PingCount = 4;
181 DataSize = 32;
182 DontFragment = FALSE;
183 TTLValue = 128;
184 TOSValue = 0;
185 Timeout = 1000;
186 UsePerformanceCounter = QueryPerformanceFrequency(&PerformanceCounterFrequency);
187
188 if (UsePerformanceCounter)
189 {
190 /* Performance counters may return incorrect results on some multiprocessor
191 platforms so we restrict execution on the first processor. This may fail
192 on Windows NT so we fall back to GetCurrentTick() for timing */
193 if (SetThreadAffinityMask (GetCurrentThread(), 1) == 0)
194 UsePerformanceCounter = FALSE;
195
196 /* Convert frequency to ticks per millisecond */
197 TicksPerMs.QuadPart = PerformanceCounterFrequency.QuadPart / 1000;
198 /* And to ticks per microsecond */
199 TicksPerUs.QuadPart = PerformanceCounterFrequency.QuadPart / 1000000;
200 }
201 if (!UsePerformanceCounter)
202 {
203 /* 1 tick per millisecond for GetCurrentTick() */
204 TicksPerMs.QuadPart = 1;
205 /* GetCurrentTick() cannot handle microseconds */
206 TicksPerUs.QuadPart = 1;
207 }
208 }
209
210 /* Return ULONG in a string */
211 static ULONG GetULONG(LPWSTR String)
212 {
213 UINT i, Length;
214 ULONG Value;
215 LPWSTR StopString;
216 i = 0;
217 Length = (UINT)wcslen(String);
218 while ((i < Length) && ((String[i] < L'0') || (String[i] > L'9'))) i++;
219 if ((i >= Length) || ((String[i] < L'0') || (String[i] > L'9')))
220 {
221 InvalidOption = TRUE;
222 return 0;
223 }
224 Value = wcstoul(&String[i], &StopString, 10);
225
226 return Value;
227 }
228
229 /* Return ULONG in a string. Try next paramter if not successful */
230 static ULONG GetULONG2(LPWSTR String1, LPWSTR String2, PINT i)
231 {
232 ULONG Value;
233
234 Value = GetULONG(String1);
235 if (InvalidOption)
236 {
237 InvalidOption = FALSE;
238 if (String2[0] != L'-')
239 {
240 Value = GetULONG(String2);
241 if (!InvalidOption)
242 *i += 1;
243 }
244 }
245
246 return Value;
247 }
248
249 /* Parse command line parameters */
250 static BOOL ParseCmdline(int argc, LPWSTR argv[])
251 {
252 INT i;
253 BOOL ShowUsage;
254 BOOL FoundTarget;
255 if (argc < 2)
256 ShowUsage = TRUE;
257 else
258 ShowUsage = FALSE;
259 FoundTarget = FALSE;
260 InvalidOption = FALSE;
261
262 for (i = 1; i < argc; i++)
263 {
264 if (argv[i][0] == L'-')
265 {
266 switch (argv[i][1])
267 {
268 case L't': NeverStop = TRUE; break;
269 case L'a': ResolveAddresses = TRUE; break;
270 case L'n': PingCount = GetULONG2(&argv[i][2], argv[i + 1], &i); break;
271 case L'l':
272 DataSize = GetULONG2(&argv[i][2], argv[i + 1], &i);
273 if (DataSize > ICMP_MAXSIZE - sizeof(ICMP_ECHO_PACKET) - sizeof(IPv4_HEADER))
274 {
275 FormatOutput(IDS_BAD_VALUE_OPTION_L, ICMP_MAXSIZE - \
276 (int)sizeof(ICMP_ECHO_PACKET) - \
277 (int)sizeof(IPv4_HEADER));
278 return FALSE;
279 }
280 break;
281 case L'f': DontFragment = TRUE; break;
282 case L'i': TTLValue = GetULONG2(&argv[i][2], argv[i + 1], &i); break;
283 case L'v': TOSValue = GetULONG2(&argv[i][2], argv[i + 1], &i); break;
284 case L'w': Timeout = GetULONG2(&argv[i][2], argv[i + 1], &i); break;
285 default:
286 FormatOutput(IDS_BAD_OPTION, argv[i]);
287 Usage();
288 return FALSE;
289 }
290 if (InvalidOption)
291 {
292 FormatOutput(IDS_BAD_OPTION_FORMAT, argv[i]);
293 return FALSE;
294 }
295 }
296 else
297 {
298 if (FoundTarget)
299 {
300 FormatOutput(IDS_BAD_PARAMETER, argv[i]);
301 return FALSE;
302 }
303 else
304 {
305 wcscpy(TargetName, argv[i]);
306 FoundTarget = TRUE;
307 }
308 }
309 }
310
311 if ((!ShowUsage) && (!FoundTarget))
312 {
313 FormatOutput(IDS_DEST_MUST_BE_SPECIFIED);
314 return FALSE;
315 }
316
317 if (ShowUsage)
318 {
319 Usage();
320 return FALSE;
321 }
322 return TRUE;
323 }
324
325 /* Calculate checksum of data */
326 static WORD Checksum(PUSHORT data, UINT size)
327 {
328 ULONG sum = 0;
329
330 while (size > 1)
331 {
332 sum += *data++;
333 size -= sizeof(USHORT);
334 }
335
336 if (size)
337 sum += *(UCHAR*)data;
338
339 sum = (sum >> 16) + (sum & 0xFFFF);
340 sum += (sum >> 16);
341
342 return (USHORT)(~sum);
343 }
344
345 /* Prepare to ping target */
346 static BOOL Setup(VOID)
347 {
348 WORD wVersionRequested;
349 WSADATA WsaData;
350 INT Status;
351 ULONG Addr;
352 PHOSTENT phe;
353 CHAR aTargetName[256];
354
355 wVersionRequested = MAKEWORD(2, 2);
356
357 Status = WSAStartup(wVersionRequested, &WsaData);
358 if (Status != 0)
359 {
360 FormatOutput(IDS_COULD_NOT_INIT_WINSOCK);
361 return FALSE;
362 }
363
364 IcmpSock = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, 0);
365 if (IcmpSock == INVALID_SOCKET)
366 {
367 FormatOutput(IDS_COULD_NOT_CREATE_SOCKET, WSAGetLastError());
368 return FALSE;
369 }
370
371 if (setsockopt(IcmpSock,
372 IPPROTO_IP,
373 IP_DONTFRAGMENT,
374 (const char *)&DontFragment,
375 sizeof(DontFragment)) == SOCKET_ERROR)
376 {
377 FormatOutput(IDS_SETSOCKOPT_FAILED, WSAGetLastError());
378 return FALSE;
379 }
380
381 if (setsockopt(IcmpSock,
382 IPPROTO_IP,
383 IP_TTL,
384 (const char *)&TTLValue,
385 sizeof(TTLValue)) == SOCKET_ERROR)
386 {
387 FormatOutput(IDS_SETSOCKOPT_FAILED, WSAGetLastError());
388 return FALSE;
389 }
390
391
392 if(!WideCharToMultiByte(CP_ACP, 0, TargetName, -1, aTargetName,\
393 sizeof(aTargetName), NULL, NULL))
394 {
395 FormatOutput(IDS_UNKNOWN_HOST, TargetName);
396 return FALSE;
397 }
398
399 ZeroMemory(&Target, sizeof(Target));
400 phe = NULL;
401 Addr = inet_addr(aTargetName);
402 if (Addr == INADDR_NONE)
403 {
404 phe = gethostbyname(aTargetName);
405 if (phe == NULL)
406 {
407 FormatOutput(IDS_UNKNOWN_HOST, TargetName);
408 return FALSE;
409 }
410
411 CopyMemory(&Target.sin_addr, phe->h_addr, phe->h_length);
412 Target.sin_family = phe->h_addrtype;
413 }
414 else
415 {
416 Target.sin_addr.s_addr = Addr;
417 Target.sin_family = AF_INET;
418 }
419
420
421 swprintf(TargetIP, L"%d.%d.%d.%d", Target.sin_addr.S_un.S_un_b.s_b1,\
422 Target.sin_addr.S_un.S_un_b.s_b2,\
423 Target.sin_addr.S_un.S_un_b.s_b3,\
424 Target.sin_addr.S_un.S_un_b.s_b4);
425 CurrentSeqNum = 1;
426 SentCount = 0;
427 LostCount = 0;
428 MinRTT.QuadPart = 0;
429 MaxRTT.QuadPart = 0;
430 SumRTT.QuadPart = 0;
431 MinRTTSet = FALSE;
432 return TRUE;
433 }
434
435 /* Close socket */
436 static VOID Cleanup(VOID)
437 {
438 if (IcmpSock != INVALID_SOCKET)
439 closesocket(IcmpSock);
440
441 WSACleanup();
442 }
443
444 static VOID QueryTime(PLARGE_INTEGER Time)
445 {
446 if (UsePerformanceCounter)
447 {
448 if (QueryPerformanceCounter(Time) == 0)
449 {
450 /* This should not happen, but we fall
451 back to GetCurrentTick() if it does */
452 Time->u.LowPart = (ULONG)GetTickCount();
453 Time->u.HighPart = 0;
454
455 /* 1 tick per millisecond for GetCurrentTick() */
456 TicksPerMs.QuadPart = 1;
457 /* GetCurrentTick() cannot handle microseconds */
458 TicksPerUs.QuadPart = 1;
459
460 UsePerformanceCounter = FALSE;
461 }
462 }
463 else
464 {
465 Time->u.LowPart = (ULONG)GetTickCount();
466 Time->u.HighPart = 0;
467 }
468 }
469
470 static VOID TimeToMsString(LPWSTR String, LARGE_INTEGER Time)
471 {
472 WCHAR Convstr[40];
473 LARGE_INTEGER LargeTime;
474 LPWSTR ms;
475
476 LargeTime.QuadPart = Time.QuadPart / TicksPerMs.QuadPart;
477
478 _i64tow(LargeTime.QuadPart, Convstr, 10);
479 wcscpy(String, Convstr);
480 ms = MyLoadString(IDS_MS);
481 wcscat(String, ms);
482 }
483
484 /* Locate the ICMP data and print it. Returns TRUE if the packet was good,
485 FALSE if not */
486 static BOOL DecodeResponse(PCHAR buffer, UINT size, PSOCKADDR_IN from)
487 {
488 PIPv4_HEADER IpHeader;
489 PICMP_ECHO_PACKET Icmp;
490 UINT IphLength;
491 WCHAR Time[100];
492 LARGE_INTEGER RelativeTime;
493 LARGE_INTEGER LargeTime;
494 WCHAR Sign[2];
495 WCHAR wfromIP[16];
496
497 IpHeader = (PIPv4_HEADER)buffer;
498
499 IphLength = IpHeader->IHL * 4;
500
501 if (size < IphLength + ICMP_MINSIZE)
502 {
503 #ifndef NDEBUG
504 printf("Bad size (0x%X < 0x%X)\n", size, IphLength + ICMP_MINSIZE);
505 #endif /* !NDEBUG */
506 return FALSE;
507 }
508
509 Icmp = (PICMP_ECHO_PACKET)(buffer + IphLength);
510
511 if (Icmp->Icmp.Type != ICMPMSG_ECHOREPLY)
512 {
513 #ifndef NDEBUG
514 printf("Bad ICMP type (0x%X should be 0x%X)\n", Icmp->Icmp.Type, ICMPMSG_ECHOREPLY);
515 #endif /* !NDEBUG */
516 return FALSE;
517 }
518
519 if (Icmp->Icmp.Id != (USHORT)GetCurrentProcessId())
520 {
521 #ifndef NDEBUG
522 printf("Bad ICMP id (0x%X should be 0x%X)\n", Icmp->Icmp.Id, (USHORT)GetCurrentProcessId());
523 #endif /* !NDEBUG */
524 return FALSE;
525 }
526
527 if (from->sin_addr.s_addr != Target.sin_addr.s_addr)
528 {
529 #ifndef NDEBUG
530 printf("Bad source address (%s should be %s)\n", inet_ntoa(from->sin_addr), inet_ntoa(Target.sin_addr));
531 #endif /* !NDEBUG */
532 return FALSE;
533 }
534
535 QueryTime(&LargeTime);
536
537 RelativeTime.QuadPart = (LargeTime.QuadPart - SentTime.QuadPart);
538
539 if ((RelativeTime.QuadPart / TicksPerMs.QuadPart) < 1)
540 {
541 LPWSTR ms1;
542
543 wcscpy(Sign, L"<");
544 ms1 = MyLoadString(IDS_1MS);
545 wcscpy(Time, ms1);
546 }
547 else
548 {
549 wcscpy(Sign, L"=");
550 TimeToMsString(Time, RelativeTime);
551 }
552
553
554 swprintf(wfromIP, L"%d.%d.%d.%d", from->sin_addr.S_un.S_un_b.s_b1,\
555 from->sin_addr.S_un.S_un_b.s_b2,\
556 from->sin_addr.S_un.S_un_b.s_b3,\
557 from->sin_addr.S_un.S_un_b.s_b4);
558 FormatOutput(IDS_REPLY_FROM, wfromIP,\
559 size - IphLength - (int)sizeof(ICMP_ECHO_PACKET),\
560 Sign, Time, IpHeader->TTL);
561
562 if (RelativeTime.QuadPart < MinRTT.QuadPart || !MinRTTSet)
563 {
564 MinRTT.QuadPart = RelativeTime.QuadPart;
565 MinRTTSet = TRUE;
566 }
567 if (RelativeTime.QuadPart > MaxRTT.QuadPart)
568 MaxRTT.QuadPart = RelativeTime.QuadPart;
569
570 SumRTT.QuadPart += RelativeTime.QuadPart;
571
572 return TRUE;
573 }
574
575 /* Send and receive one ping */
576 static BOOL Ping(VOID)
577 {
578 INT Status;
579 SOCKADDR From;
580 INT Length;
581 PVOID Buffer;
582 UINT Size;
583 PICMP_ECHO_PACKET Packet;
584
585 /* Account for extra space for IP header when packet is received */
586 Size = DataSize + 128;
587 Buffer = GlobalAlloc(0, Size);
588 if (!Buffer)
589 {
590 FormatOutput(IDS_NOT_ENOUGH_RESOURCES);
591 return FALSE;
592 }
593
594 ZeroMemory(Buffer, Size);
595 Packet = (PICMP_ECHO_PACKET)Buffer;
596
597 /* Assemble ICMP echo request packet */
598 Packet->Icmp.Type = ICMPMSG_ECHOREQUEST;
599 Packet->Icmp.Code = 0;
600 Packet->Icmp.Id = (USHORT)GetCurrentProcessId();
601 Packet->Icmp.SeqNum = htons((USHORT)CurrentSeqNum);
602 Packet->Icmp.Checksum = 0;
603
604 /* Calculate checksum for ICMP header and data area */
605 Packet->Icmp.Checksum = Checksum((PUSHORT)&Packet->Icmp, sizeof(ICMP_ECHO_PACKET) + DataSize);
606
607 CurrentSeqNum++;
608
609 /* Send ICMP echo request */
610
611 FD_ZERO(&Fds);
612 FD_SET(IcmpSock, &Fds);
613 Timeval.tv_sec = Timeout / 1000;
614 Timeval.tv_usec = Timeout % 1000;
615 Status = select(0, NULL, &Fds, NULL, &Timeval);
616 if ((Status != SOCKET_ERROR) && (Status != 0))
617 {
618
619 #ifndef NDEBUG
620 printf("Sending packet\n");
621 DisplayBuffer(Buffer, sizeof(ICMP_ECHO_PACKET) + DataSize);
622 printf("\n");
623 #endif /* !NDEBUG */
624
625 Status = sendto(IcmpSock, Buffer, sizeof(ICMP_ECHO_PACKET) + DataSize,
626 0, (SOCKADDR*)&Target, sizeof(Target));
627 QueryTime(&SentTime);
628 SentCount++;
629 }
630 if (Status == SOCKET_ERROR)
631 {
632 if (WSAGetLastError() == WSAEHOSTUNREACH)
633 FormatOutput(IDS_DEST_UNREACHABLE);
634 else
635 FormatOutput(IDS_COULD_NOT_TRANSMIT, WSAGetLastError());
636 GlobalFree(Buffer);
637 return FALSE;
638 }
639
640 /* Expect to receive ICMP echo reply */
641 FD_ZERO(&Fds);
642 FD_SET(IcmpSock, &Fds);
643 Timeval.tv_sec = Timeout / 1000;
644 Timeval.tv_usec = Timeout % 1000;
645
646 do {
647 Status = select(0, &Fds, NULL, NULL, &Timeval);
648 if ((Status != SOCKET_ERROR) && (Status != 0))
649 {
650 Length = sizeof(From);
651 Status = recvfrom(IcmpSock, Buffer, Size, 0, &From, &Length);
652
653 #ifndef NDEBUG
654 printf("Received packet\n");
655 DisplayBuffer(Buffer, Status);
656 printf("\n");
657 #endif /* !NDEBUG */
658 }
659 else
660 LostCount++;
661 if (Status == SOCKET_ERROR)
662 {
663 if (WSAGetLastError() != WSAETIMEDOUT)
664 {
665 FormatOutput(IDS_COULD_NOT_RECV, WSAGetLastError());
666 GlobalFree(Buffer);
667 return FALSE;
668 }
669 Status = 0;
670 }
671
672 if (Status == 0)
673 {
674 FormatOutput(IDS_REQUEST_TIMEOUT);
675 GlobalFree(Buffer);
676 return TRUE;
677 }
678
679 } while (!DecodeResponse(Buffer, Status, (PSOCKADDR_IN)&From));
680
681 GlobalFree(Buffer);
682 return TRUE;
683 }
684
685
686 /* Program entry point */
687 int wmain(int argc, LPWSTR argv[])
688 {
689 UINT Count;
690 WCHAR MinTime[20];
691 WCHAR MaxTime[20];
692 WCHAR AvgTime[20];
693
694 hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
695
696 Reset();
697
698 if ((ParseCmdline(argc, argv)) && (Setup()))
699 {
700
701 FormatOutput(IDS_PING_WITH_BYTES, TargetName, TargetIP, DataSize);
702
703 Count = 0;
704 while ((NeverStop) || (Count < PingCount))
705 {
706 Ping();
707 Count++;
708 if((NeverStop) || (Count < PingCount))
709 Sleep(Timeout);
710 };
711
712 Cleanup();
713
714 /* Calculate avarage round trip time */
715 if ((SentCount - LostCount) > 0)
716 AvgRTT.QuadPart = SumRTT.QuadPart / (SentCount - LostCount);
717 else
718 AvgRTT.QuadPart = 0;
719
720 /* Calculate loss percent */
721 Count = SentCount ? (LostCount * 100) / SentCount : 0;
722
723 if (!MinRTTSet)
724 MinRTT = MaxRTT;
725
726 TimeToMsString(MinTime, MinRTT);
727 TimeToMsString(MaxTime, MaxRTT);
728 TimeToMsString(AvgTime, AvgRTT);
729
730 /* Print statistics */
731 FormatOutput(IDS_PING_STATISTICS, TargetIP);
732 FormatOutput(IDS_PACKETS_SENT_RECEIVED_LOST,\
733 SentCount, SentCount - LostCount, LostCount, Count);
734
735 /* Print approximate times or NO approximate times if 100% loss */
736 if ((SentCount - LostCount) > 0)
737 {
738 FormatOutput(IDS_APPROXIMATE_ROUND_TRIP);
739 FormatOutput(IDS_MIN_MAX_AVERAGE, MinTime, MaxTime, AvgTime);
740 }
741 }
742 return 0;
743 }
744
745 /* EOF */