merge trunk head (37902)
[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 %I64d.\n",
224 ICMP_MAXSIZE - 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 = 0;
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 QueryTime(&LargeTime);
441
442 RelativeTime.QuadPart = (LargeTime.QuadPart - Icmp->Timestamp.QuadPart);
443
444 if ((RelativeTime.QuadPart / TicksPerMs.QuadPart) < 1)
445 {
446 strcpy(Sign, "<");
447 strcpy(Time, "1ms");
448 }
449 else
450 {
451 strcpy(Sign, "=");
452 TimeToMsString(Time, RelativeTime);
453 }
454
455
456 printf("Reply from %s: bytes=%I64d time%s%s TTL=%d\n", inet_ntoa(from->sin_addr),
457 size - IphLength - sizeof(ICMP_ECHO_PACKET), Sign, Time, IpHeader->TTL);
458 if (RelativeTime.QuadPart < MinRTT.QuadPart || !MinRTTSet)
459 {
460 MinRTT.QuadPart = RelativeTime.QuadPart;
461 MinRTTSet = TRUE;
462 }
463 if (RelativeTime.QuadPart > MaxRTT.QuadPart)
464 MaxRTT.QuadPart = RelativeTime.QuadPart;
465
466 SumRTT.QuadPart += RelativeTime.QuadPart;
467
468 return TRUE;
469 }
470
471 /* Send and receive one ping */
472 static BOOL Ping(VOID)
473 {
474 INT Status;
475 SOCKADDR From;
476 INT Length;
477 PVOID Buffer;
478 UINT Size;
479 PICMP_ECHO_PACKET Packet;
480
481 /* Account for extra space for IP header when packet is received */
482 Size = DataSize + 128;
483 Buffer = GlobalAlloc(0, Size);
484 if (!Buffer)
485 {
486 printf("Not enough free resources available.\n");
487 return FALSE;
488 }
489
490 ZeroMemory(Buffer, Size);
491 Packet = (PICMP_ECHO_PACKET)Buffer;
492
493 /* Assemble ICMP echo request packet */
494 Packet->Icmp.Type = ICMPMSG_ECHOREQUEST;
495 Packet->Icmp.Code = 0;
496 Packet->Icmp.Id = (USHORT)GetCurrentProcessId();
497 Packet->Icmp.SeqNum = htons((USHORT)CurrentSeqNum);
498 Packet->Icmp.Checksum = 0;
499
500 /* Timestamp is part of data area */
501 QueryTime(&Packet->Timestamp);
502
503 CopyMemory(Buffer, &Packet->Icmp, sizeof(ICMP_ECHO_PACKET) + DataSize);
504 /* Calculate checksum for ICMP header and data area */
505 Packet->Icmp.Checksum = Checksum((PUSHORT)&Packet->Icmp, sizeof(ICMP_ECHO_PACKET) + DataSize);
506
507 CurrentSeqNum++;
508
509 /* Send ICMP echo request */
510
511 FD_ZERO(&Fds);
512 FD_SET(IcmpSock, &Fds);
513 Timeval.tv_sec = Timeout / 1000;
514 Timeval.tv_usec = Timeout % 1000;
515 Status = select(0, NULL, &Fds, NULL, &Timeval);
516 if ((Status != SOCKET_ERROR) && (Status != 0))
517 {
518
519 #ifndef NDEBUG
520 printf("Sending packet\n");
521 DisplayBuffer(Buffer, sizeof(ICMP_ECHO_PACKET) + DataSize);
522 printf("\n");
523 #endif /* !NDEBUG */
524
525 Status = sendto(IcmpSock, Buffer, sizeof(ICMP_ECHO_PACKET) + DataSize,
526 0, (SOCKADDR*)&Target, sizeof(Target));
527 SentCount++;
528 }
529 if (Status == SOCKET_ERROR)
530 {
531 LostCount++;
532 if (WSAGetLastError() == WSAEHOSTUNREACH)
533 printf("Destination host unreachable.\n");
534 else
535 printf("Could not transmit data (%d).\n", WSAGetLastError());
536 GlobalFree(Buffer);
537 return FALSE;
538 }
539
540 /* Expect to receive ICMP echo reply */
541 FD_ZERO(&Fds);
542 FD_SET(IcmpSock, &Fds);
543 Timeval.tv_sec = Timeout / 1000;
544 Timeval.tv_usec = Timeout % 1000;
545
546 Status = select(0, &Fds, NULL, NULL, &Timeval);
547 if ((Status != SOCKET_ERROR) && (Status != 0))
548 {
549 Length = sizeof(From);
550 Status = recvfrom(IcmpSock, Buffer, Size, 0, &From, &Length);
551
552 #ifndef NDEBUG
553 printf("Received packet\n");
554 DisplayBuffer(Buffer, Status);
555 printf("\n");
556 #endif /* !NDEBUG */
557 }
558 else
559 LostCount++;
560 if (Status == SOCKET_ERROR)
561 {
562 if (WSAGetLastError() != WSAETIMEDOUT)
563 {
564 printf("Could not receive data (%d).\n", WSAGetLastError());
565 GlobalFree(Buffer);
566 return FALSE;
567 }
568 Status = 0;
569 }
570
571 if (Status == 0)
572 {
573 printf("Request timed out.\n");
574 GlobalFree(Buffer);
575 return TRUE;
576 }
577
578 if (!DecodeResponse(Buffer, Status, (PSOCKADDR_IN)&From))
579 {
580 /* FIXME: Wait again as it could be another ICMP message type */
581 printf("Request timed out (incomplete datagram received).\n");
582 LostCount++;
583 }
584
585 GlobalFree(Buffer);
586 return TRUE;
587 }
588
589
590 /* Program entry point */
591 int main(int argc, char* argv[])
592 {
593 UINT Count;
594 CHAR MinTime[20];
595 CHAR MaxTime[20];
596 CHAR AvgTime[20];
597
598 Reset();
599
600 if ((ParseCmdline(argc, argv)) && (Setup()))
601 {
602
603 printf("\nPinging %s [%s] with %d bytes of data:\n\n",
604 TargetName, TargetIP, DataSize);
605
606 Count = 0;
607 while ((NeverStop) || (Count < PingCount))
608 {
609 Ping();
610 Sleep(Timeout);
611 Count++;
612 };
613
614 Cleanup();
615
616 /* Calculate avarage round trip time */
617 if ((SentCount - LostCount) > 0)
618 AvgRTT.QuadPart = SumRTT.QuadPart / (SentCount - LostCount);
619 else
620 AvgRTT.QuadPart = 0;
621
622 /* Calculate loss percent */
623 Count = SentCount ? (LostCount * 100) / SentCount : 0;
624
625 if (!MinRTTSet)
626 MinRTT = MaxRTT;
627
628 TimeToMsString(MinTime, MinRTT);
629 TimeToMsString(MaxTime, MaxRTT);
630 TimeToMsString(AvgTime, AvgRTT);
631
632 /* Print statistics */
633 printf("\nPing statistics for %s:\n", TargetIP);
634 printf(" Packets: Sent = %d, Received = %d, Lost = %d (%d%% loss),\n",
635 SentCount, SentCount - LostCount, LostCount, Count);
636 /* Print approximate times or NO approximate times if 100% loss */
637 if ((SentCount - LostCount) > 0)
638 {
639 printf("Approximate round trip times in milli-seconds:\n");
640 printf(" Minimum = %s, Maximum = %s, Average = %s\n",
641 MinTime, MaxTime, AvgTime);
642 }
643 }
644 return 0;
645 }
646
647 /* EOF */