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