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