display correct Min RTT
[reactos.git] / reactos / apps / utils / net / ping / ping.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS ping utility
4 * FILE: apps/net/ping/ping.c
5 * PURPOSE: Network test utility
6 * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
7 * REVISIONS:
8 * CSH 01/09/2000 Created
9 */
10
11 #include <winsock2.h>
12 #include <tchar.h>
13 #include <stdarg.h>
14 #include <string.h>
15 #include <stdio.h>
16
17 #ifndef _MSC_VER
18
19 /* FIXME: Where should this be? */
20 #ifdef CopyMemory
21 #undef CopyMemory
22 #endif
23 #define CopyMemory(Destination, Source, Length) memcpy(Destination, Source, Length);
24
25 /* Should be in the header files somewhere (exported by ntdll.dll) */
26 long atol(const char *str);
27
28 #ifndef __int64
29 typedef long long __int64;
30 #endif
31
32 char * _i64toa(__int64 value, char *string, int radix);
33
34 #endif /* _MSC_VER */
35
36 #ifdef DBG
37 #undef DBG
38 #endif
39
40 /* General ICMP constants */
41 #define ICMP_MINSIZE 8 /* Minimum ICMP packet size */
42 #define ICMP_MAXSIZE 65535 /* Maximum ICMP packet size */
43
44 /* ICMP message types */
45 #define ICMPMSG_ECHOREQUEST 8 /* ICMP ECHO request message */
46 #define ICMPMSG_ECHOREPLY 0 /* ICMP ECHO reply message */
47
48 #pragma pack(4)
49
50 /* IPv4 header structure */
51 typedef struct _IPv4_HEADER {
52 unsigned char IHL:4;
53 unsigned char Version:4;
54 unsigned char TOS;
55 unsigned short Length;
56 unsigned short Id;
57 unsigned short FragFlags;
58 unsigned char TTL;
59 unsigned char Protocol;
60 unsigned short Checksum;
61 unsigned int SrcAddress;
62 unsigned int DstAddress;
63 } IPv4_HEADER, *PIPv4_HEADER;
64
65 /* ICMP echo request/reply header structure */
66 typedef struct _ICMP_HEADER {
67 unsigned char Type;
68 unsigned char Code;
69 unsigned short Checksum;
70 unsigned short Id;
71 unsigned short SeqNum;
72 } ICMP_HEADER, *PICMP_HEADER;
73
74 typedef struct _ICMP_ECHO_PACKET {
75 ICMP_HEADER Icmp;
76 LARGE_INTEGER Timestamp;
77 } ICMP_ECHO_PACKET, *PICMP_ECHO_PACKET;
78
79 #pragma pack(1)
80
81 BOOL InvalidOption;
82 BOOL NeverStop;
83 BOOL ResolveAddresses;
84 UINT PingCount;
85 UINT DataSize; /* ICMP echo request data size */
86 BOOL DontFragment;
87 ULONG TTLValue;
88 ULONG TOSValue;
89 ULONG Timeout;
90 CHAR TargetName[256];
91 SOCKET IcmpSock;
92 SOCKADDR_IN Target;
93 LPSTR TargetIP;
94 FD_SET Fds;
95 TIMEVAL Timeval;
96 UINT CurrentSeqNum;
97 UINT SentCount;
98 UINT LostCount;
99 BOOL MinRTTSet;
100 LARGE_INTEGER MinRTT; /* Minimum round trip time in microseconds */
101 LARGE_INTEGER MaxRTT;
102 LARGE_INTEGER SumRTT;
103 LARGE_INTEGER AvgRTT;
104 LARGE_INTEGER TicksPerMs; /* Ticks per millisecond */
105 LARGE_INTEGER TicksPerUs; /* Ticks per microsecond */
106 BOOL UsePerformanceCounter;
107
108 #ifdef DBG
109 /* Display the contents of a buffer */
110 static VOID DisplayBuffer(
111 PVOID Buffer,
112 DWORD Size)
113 {
114 UINT i;
115 PCHAR p;
116
117 printf("Buffer (0x%p) Size (0x%lX).\n", Buffer, Size);
118
119 p = (PCHAR)Buffer;
120 for (i = 0; i < Size; i++) {
121 if (i % 16 == 0) {
122 printf("\n");
123 }
124 printf("%02X ", (p[i]) & 0xFF);
125 }
126 }
127 #endif /* DBG */
128
129 /* Display usage information on screen */
130 static VOID Usage(VOID)
131 {
132 printf("\nUsage: ping [-t] [-n count] [-l size] [-w timeout] destination-host\n\n");
133 printf("Options:\n");
134 printf(" -t Ping the specified host until stopped.\n");
135 printf(" To stop - type Control-C.\n");
136 printf(" -n count Number of echo requests to send.\n");
137 printf(" -l size Send buffer size.\n");
138 printf(" -w timeout Timeout in milliseconds to wait for each reply.\n\n");
139 }
140
141 /* Reset configuration to default values */
142 static VOID Reset(VOID)
143 {
144 LARGE_INTEGER PerformanceCounterFrequency;
145
146 NeverStop = FALSE;
147 ResolveAddresses = FALSE;
148 PingCount = 4;
149 DataSize = 32;
150 DontFragment = FALSE;
151 TTLValue = 128;
152 TOSValue = 0;
153 Timeout = 1000;
154 UsePerformanceCounter = QueryPerformanceFrequency(&PerformanceCounterFrequency);
155
156 if (UsePerformanceCounter) {
157 /* Performance counters may return incorrect results on some multiprocessor
158 platforms so we restrict execution on the first processor. This may fail
159 on Windows NT so we fall back to GetCurrentTick() for timing */
160 if (SetThreadAffinityMask (GetCurrentThread(), 1) == 0) {
161 UsePerformanceCounter = FALSE;
162 }
163
164 /* Convert frequency to ticks per millisecond */
165 TicksPerMs.QuadPart = PerformanceCounterFrequency.QuadPart / 1000;
166 /* And to ticks per microsecond */
167 TicksPerUs.QuadPart = PerformanceCounterFrequency.QuadPart / 1000000;
168 }
169 if (!UsePerformanceCounter) {
170 /* 1 tick per millisecond for GetCurrentTick() */
171 TicksPerMs.QuadPart = 1;
172 /* GetCurrentTick() cannot handle microseconds */
173 TicksPerUs.QuadPart = 1;
174 }
175 }
176
177 /* Return ULONG in a string */
178 static ULONG GetULONG(LPSTR String)
179 {
180 UINT i, Length;
181 ULONG Value;
182
183 i = 0;
184 Length = (UINT)_tcslen(String);
185 while ((i < Length) && ((String[i] < '0') || (String[i] > '9'))) i++;
186 if ((i >= Length) || ((String[i] < '0') || (String[i] > '9'))) {
187 InvalidOption = TRUE;
188 return 0;
189 }
190 Value = (ULONG)atol(&String[i]);
191
192 return Value;
193 }
194
195 /* Return ULONG in a string. Try next paramter if not successful */
196 static ULONG GetULONG2(LPSTR String1, LPSTR String2, PINT i)
197 {
198 ULONG Value;
199
200 Value = GetULONG(String1);
201 if (InvalidOption) {
202 InvalidOption = FALSE;
203 if (String2[0] != '-') {
204 Value = GetULONG(String2);
205 if (!InvalidOption)
206 *i += 1;
207 }
208 }
209
210 return Value;
211 }
212
213 /* Parse command line parameters */
214 static BOOL ParseCmdline(int argc, char* argv[])
215 {
216 INT i;
217 BOOL ShowUsage;
218 BOOL FoundTarget;
219 //#if 1
220 // lstrcpy(TargetName, "127.0.0.1");
221 // PingCount = 1;
222 // return TRUE;
223 //#endif
224 if (argc < 2) {
225 ShowUsage = TRUE;
226 } else {
227 ShowUsage = FALSE;
228 }
229 FoundTarget = FALSE;
230 InvalidOption = FALSE;
231
232 for (i = 1; i < argc; i++) {
233 if (argv[i][0] == '-') {
234 switch (argv[i][1]) {
235 case 't': NeverStop = TRUE; break;
236 case 'a': ResolveAddresses = TRUE; break;
237 case 'n': PingCount = GetULONG2(&argv[i][2], argv[i + 1], &i); break;
238 case 'l':
239 DataSize = GetULONG2(&argv[i][2], argv[i + 1], &i);
240 if ((DataSize < 0) || (DataSize > ICMP_MAXSIZE - sizeof(ICMP_ECHO_PACKET))) {
241 printf("Bad value for option -l, valid range is from 0 to %d.\n",
242 ICMP_MAXSIZE - sizeof(ICMP_ECHO_PACKET));
243 return FALSE;
244 }
245 break;
246 case 'f': DontFragment = TRUE; break;
247 case 'i': TTLValue = GetULONG2(&argv[i][2], argv[i + 1], &i); break;
248 case 'v': TOSValue = GetULONG2(&argv[i][2], argv[i + 1], &i); break;
249 case 'w': Timeout = GetULONG2(&argv[i][2], argv[i + 1], &i); break;
250 default:
251 printf("Bad option %s.\n", argv[i]);
252 Usage();
253 return FALSE;
254 }
255 if (InvalidOption) {
256 printf("Bad option format %s.\n", argv[i]);
257 return FALSE;
258 }
259 } else {
260 if (FoundTarget) {
261 printf("Bad parameter %s.\n", argv[i]);
262 return FALSE;
263 } else {
264 lstrcpy(TargetName, argv[i]);
265 FoundTarget = TRUE;
266 }
267 }
268 }
269
270 if ((!ShowUsage) && (!FoundTarget)) {
271 printf("Name or IP address of destination host must be specified.\n");
272 return FALSE;
273 }
274
275 if (ShowUsage) {
276 Usage();
277 return FALSE;
278 }
279 return TRUE;
280 }
281
282 /* Calculate checksum of data */
283 static WORD Checksum(PUSHORT data, UINT size)
284 {
285 ULONG sum = 0;
286
287 while (size > 1) {
288 sum += *data++;
289 size -= sizeof(USHORT);
290 }
291
292 if (size)
293 sum += *(UCHAR*)data;
294
295 sum = (sum >> 16) + (sum & 0xFFFF);
296 sum += (sum >> 16);
297
298 return (USHORT)(~sum);
299 }
300
301 /* Prepare to ping target */
302 static BOOL Setup(VOID)
303 {
304 WORD wVersionRequested;
305 WSADATA WsaData;
306 INT Status;
307 ULONG Addr;
308 PHOSTENT phe;
309
310 wVersionRequested = MAKEWORD(2, 2);
311
312 Status = WSAStartup(wVersionRequested, &WsaData);
313 if (Status != 0) {
314 printf("Could not initialize winsock dll.\n");
315 return FALSE;
316 }
317
318 IcmpSock = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, 0);
319 if (IcmpSock == INVALID_SOCKET) {
320 printf("Could not create socket (#%d).\n", WSAGetLastError());
321 return FALSE;
322 }
323
324 ZeroMemory(&Target, sizeof(Target));
325 phe = NULL;
326 Addr = inet_addr(TargetName);
327 if (Addr == INADDR_NONE) {
328 phe = gethostbyname(TargetName);
329 if (phe == NULL) {
330 printf("Unknown host %s.\n", TargetName);
331 return FALSE;
332 }
333 }
334
335 if (phe != NULL) {
336 CopyMemory(&Target.sin_addr, phe->h_addr, phe->h_length);
337 } else {
338 Target.sin_addr.s_addr = Addr;
339 }
340
341 if (phe != NULL) {
342 Target.sin_family = phe->h_addrtype;
343 } else {
344 Target.sin_family = AF_INET;
345 }
346
347 TargetIP = inet_ntoa(Target.sin_addr);
348 CurrentSeqNum = 0;
349 SentCount = 0;
350 LostCount = 0;
351 MinRTT.QuadPart = 0;
352 MaxRTT.QuadPart = 0;
353 SumRTT.QuadPart = 0;
354 MinRTTSet = FALSE;
355 return TRUE;
356 }
357
358 /* Close socket */
359 static VOID Cleanup(VOID)
360 {
361 if (IcmpSock != INVALID_SOCKET)
362 closesocket(IcmpSock);
363
364 WSACleanup();
365 }
366
367 static VOID QueryTime(PLARGE_INTEGER Time)
368 {
369 if (UsePerformanceCounter) {
370 if (QueryPerformanceCounter(Time) == 0) {
371 /* This should not happen, but we fall
372 back to GetCurrentTick() if it does */
373 Time->u.LowPart = (ULONG)GetTickCount();
374 Time->u.HighPart = 0;
375
376 /* 1 tick per millisecond for GetCurrentTick() */
377 TicksPerMs.QuadPart = 1;
378 /* GetCurrentTick() cannot handle microseconds */
379 TicksPerUs.QuadPart = 1;
380
381 UsePerformanceCounter = FALSE;
382 }
383 } else {
384 Time->u.LowPart = (ULONG)GetTickCount();
385 Time->u.HighPart = 0;
386 }
387 }
388
389 static VOID TimeToMsString(LPSTR String, LARGE_INTEGER Time)
390 {
391 CHAR Convstr[40];
392 LARGE_INTEGER LargeTime;
393
394 LargeTime.QuadPart = Time.QuadPart / TicksPerMs.QuadPart;
395
396 _i64toa(LargeTime.QuadPart, Convstr, 10);
397 strcpy(String, Convstr);
398 strcat(String, "ms");
399 }
400
401 /* Locate the ICMP data and print it. Returns TRUE if the packet was good,
402 FALSE if not */
403 static BOOL DecodeResponse(PCHAR buffer, UINT size, PSOCKADDR_IN from)
404 {
405 PIPv4_HEADER IpHeader;
406 PICMP_ECHO_PACKET Icmp;
407 UINT IphLength;
408 CHAR Time[100];
409 LARGE_INTEGER RelativeTime;
410 LARGE_INTEGER LargeTime;
411 CHAR Sign[1];
412
413 IpHeader = (PIPv4_HEADER)buffer;
414
415 IphLength = IpHeader->IHL * 4;
416
417 if (size < IphLength + ICMP_MINSIZE) {
418 #ifdef DBG
419 printf("Bad size (0x%X < 0x%X)\n", size, IphLength + ICMP_MINSIZE);
420 #endif /* DBG */
421 return FALSE;
422 }
423
424 Icmp = (PICMP_ECHO_PACKET)(buffer + IphLength);
425
426 if (Icmp->Icmp.Type != ICMPMSG_ECHOREPLY) {
427 #ifdef DBG
428 printf("Bad ICMP type (0x%X should be 0x%X)\n", Icmp->Icmp.Type, ICMPMSG_ECHOREPLY);
429 #endif /* DBG */
430 return FALSE;
431 }
432
433 if (Icmp->Icmp.Id != (USHORT)GetCurrentProcessId()) {
434 #ifdef DBG
435 printf("Bad ICMP id (0x%X should be 0x%X)\n", Icmp->Icmp.Id, (USHORT)GetCurrentProcessId());
436 #endif /* DBG */
437 return FALSE;
438 }
439
440 QueryTime(&LargeTime);
441
442 RelativeTime.QuadPart = (LargeTime.QuadPart - Icmp->Timestamp.QuadPart);
443
444 if ((RelativeTime.QuadPart / TicksPerMs.QuadPart) < 1) {
445 strcpy(Sign, "<");
446 strcpy(Time, "1ms");
447 } else {
448 strcpy(Sign, "=");
449 TimeToMsString(Time, RelativeTime);
450 }
451
452
453 printf("Reply from %s: bytes=%d time%s%s TTL=%d\n", inet_ntoa(from->sin_addr),
454 size - IphLength - sizeof(ICMP_ECHO_PACKET), Sign, Time, IpHeader->TTL);
455 if (RelativeTime.QuadPart < MinRTT.QuadPart || !MinRTTSet) {
456 MinRTT.QuadPart = RelativeTime.QuadPart;
457 MinRTTSet = TRUE;
458 }
459 if (RelativeTime.QuadPart > MaxRTT.QuadPart)
460 MaxRTT.QuadPart = RelativeTime.QuadPart;
461
462 SumRTT.QuadPart += RelativeTime.QuadPart;
463
464 return TRUE;
465 }
466
467 /* Send and receive one ping */
468 static BOOL Ping(VOID)
469 {
470 INT Status;
471 SOCKADDR From;
472 INT Length;
473 PVOID Buffer;
474 UINT Size;
475 PICMP_ECHO_PACKET Packet;
476
477 /* Account for extra space for IP header when packet is received */
478 Size = DataSize + 128;
479 Buffer = GlobalAlloc(0, Size);
480 if (!Buffer) {
481 printf("Not enough free resources available.\n");
482 return FALSE;
483 }
484
485 ZeroMemory(Buffer, Size);
486 Packet = (PICMP_ECHO_PACKET)Buffer;
487
488 /* Assemble ICMP echo request packet */
489 Packet->Icmp.Type = ICMPMSG_ECHOREQUEST;
490 Packet->Icmp.Code = 0;
491 Packet->Icmp.Id = (USHORT)GetCurrentProcessId();
492 Packet->Icmp.SeqNum = (USHORT)CurrentSeqNum;
493 Packet->Icmp.Checksum = 0;
494
495 /* Timestamp is part of data area */
496 QueryTime(&Packet->Timestamp);
497
498 CopyMemory(Buffer, &Packet->Icmp, sizeof(ICMP_ECHO_PACKET) + DataSize);
499 /* Calculate checksum for ICMP header and data area */
500 Packet->Icmp.Checksum = Checksum((PUSHORT)&Packet->Icmp, sizeof(ICMP_ECHO_PACKET) + DataSize);
501
502 CurrentSeqNum++;
503
504 /* Send ICMP echo request */
505
506 FD_ZERO(&Fds);
507 FD_SET(IcmpSock, &Fds);
508 Timeval.tv_sec = Timeout / 1000;
509 Timeval.tv_usec = Timeout % 1000;
510 Status = select(0, NULL, &Fds, NULL, &Timeval);
511 if ((Status != SOCKET_ERROR) && (Status != 0)) {
512
513 #ifdef DBG
514 printf("Sending packet\n");
515 DisplayBuffer(Buffer, sizeof(ICMP_ECHO_PACKET) + DataSize);
516 printf("\n");
517 #endif /* DBG */
518
519 Status = sendto(IcmpSock, Buffer, sizeof(ICMP_ECHO_PACKET) + DataSize,
520 0, (SOCKADDR*)&Target, sizeof(Target));
521 SentCount++;
522 }
523 if (Status == SOCKET_ERROR) {
524 if (WSAGetLastError() == WSAEHOSTUNREACH) {
525 printf("Destination host unreachable.\n");
526 } else {
527 printf("Could not transmit data (%d).\n", WSAGetLastError());
528 }
529 GlobalFree(Buffer);
530 return FALSE;
531 }
532
533 /* Expect to receive ICMP echo reply */
534 FD_ZERO(&Fds);
535 FD_SET(IcmpSock, &Fds);
536 Timeval.tv_sec = Timeout / 1000;
537 Timeval.tv_usec = Timeout % 1000;
538
539 Status = select(0, &Fds, NULL, NULL, &Timeval);
540 if ((Status != SOCKET_ERROR) && (Status != 0)) {
541 Length = sizeof(From);
542 Status = recvfrom(IcmpSock, Buffer, Size, 0, &From, &Length);
543
544 #ifdef DBG
545 printf("Received packet\n");
546 DisplayBuffer(Buffer, Status);
547 printf("\n");
548 #endif /* DBG */
549 }
550 if (Status == SOCKET_ERROR) {
551 if (WSAGetLastError() != WSAETIMEDOUT) {
552 printf("Could not receive data (%d).\n", WSAGetLastError());
553 GlobalFree(Buffer);
554 return FALSE;
555 }
556 Status = 0;
557 }
558
559 if (Status == 0) {
560 printf("Request timed out.\n");
561 LostCount++;
562 GlobalFree(Buffer);
563 return TRUE;
564 }
565
566 if (!DecodeResponse(Buffer, Status, (PSOCKADDR_IN)&From)) {
567 /* FIXME: Wait again as it could be another ICMP message type */
568 printf("Request timed out (incomplete datagram received).\n");
569 LostCount++;
570 }
571
572 GlobalFree(Buffer);
573 return TRUE;
574 }
575
576
577 /* Program entry point */
578 int main(int argc, char* argv[])
579 {
580 UINT Count;
581 CHAR MinTime[20];
582 CHAR MaxTime[20];
583 CHAR AvgTime[20];
584
585 Reset();
586
587 if ((ParseCmdline(argc, argv)) && (Setup())) {
588
589 printf("\nPinging %s [%s] with %d bytes of data:\n\n",
590 TargetName, TargetIP, DataSize);
591
592 Count = 0;
593 while ((NeverStop) || (Count < PingCount)) {
594 Ping();
595 Sleep(Timeout);
596 Count++;
597 };
598
599 Cleanup();
600
601 /* Calculate avarage round trip time */
602 if ((SentCount - LostCount) > 0) {
603 AvgRTT.QuadPart = SumRTT.QuadPart / (SentCount - LostCount);
604 } else {
605 AvgRTT.QuadPart = 0;
606 }
607
608 /* Calculate loss percent */
609 if (LostCount > 0) {
610 Count = (SentCount * 100) / LostCount;
611 } else {
612 Count = 0;
613 }
614
615 if (!MinRTTSet)
616 MinRTT = MaxRTT;
617
618 TimeToMsString(MinTime, MinRTT);
619 TimeToMsString(MaxTime, MaxRTT);
620 TimeToMsString(AvgTime, AvgRTT);
621
622 /* Print statistics */
623 printf("\nPing statistics for %s:\n", TargetIP);
624 printf(" Packets: Sent = %d, Received = %d, Lost = %d (%d%% loss),\n",
625 SentCount, SentCount - LostCount, LostCount, Count);
626 printf("Approximate round trip times in milli-seconds:\n");
627 printf(" Minimum = %s, Maximum = %s, Average = %s\n",
628 MinTime, MaxTime, AvgTime);
629 }
630 return 0;
631 }
632
633 /* EOF */