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