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