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