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