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