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