Added new better tracert replacement by Ged Murphy
[reactos.git] / reactos / apps / utils / net / tracert / tracert.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS traceroute utility
4 * FILE: apps/utils/net/tracert/tracert.c
5 * PURPOSE: trace a packets route through a network
6 * PROGRAMMERS: Ged Murphy (gedmurphy@gmail.com)
7 * REVISIONS:
8 * GM 03/05/05 Created
9 *
10 */
11
12
13 #include <windows.h>
14 #include <winsock2.h>
15 #include <tchar.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <ws2tcpip.h>
19 #include <string.h>
20 #include <time.h>
21 #include "tracert.h"
22
23 #define WIN32_LEAN_AND_MEAN
24
25 #ifdef DBG
26 #undef DBG
27 #endif
28
29 /*
30 * globals
31 */
32 SOCKET icmpSock; // socket descriptor
33 SOCKADDR_IN source, dest; // source and destination address info
34 ECHO_REPLY_HEADER sendpacket; // ICMP echo packet
35 IPv4_HEADER recvpacket; // return reveive packet
36
37 BOOL bUsePerformanceCounter; // whether to use the high res performance counter
38 LARGE_INTEGER TicksPerMs; // number of millisecs in relation to proc freq
39 LARGE_INTEGER TicksPerUs; // number of microsecs in relation to proc freq
40 LONGLONG lTimeStart; // send packet, timer start
41 LONGLONG lTimeEnd; // receive packet, timer end
42
43 CHAR cHostname[256]; // target hostname
44 CHAR cDestIP[18]; // target IP
45
46
47 /*
48 * command line options
49 */
50 BOOL bResolveAddresses = TRUE; // -d MS ping defaults to true.
51 INT iMaxHops = 30; // -h Max number of hops before trace ends
52 INT iHostList; // -j @UNIMPLEMENTED@
53 INT iTimeOut = 2000; // -w time before packet times out
54
55
56
57
58 /*
59 *
60 * Parse command line parameters and set any options
61 *
62 */
63 BOOL ParseCmdline(int argc, char* argv[])
64 {
65 int i;
66
67 if (argc < 2)
68 {
69 Usage();
70 return FALSE;
71 }
72
73 for (i = 1; i < argc; i++) {
74 if (argv[i][0] == '-') {
75 switch (argv[i][1]) {
76 case 'd': bResolveAddresses = FALSE;
77 break;
78 case 'h': sscanf(argv[i+1], "%d", &iMaxHops);
79 break;
80 case 'l': break; /* @unimplemented@ */
81 case 'w': sscanf(argv[i+1], "%d", &iTimeOut);
82 break;
83 default:
84 _tprintf(_T("%s is not a valid option.\n"), argv[i]);
85 Usage();
86 return FALSE;
87 }
88 } else {
89 /* copy target address */
90 strncpy(cHostname, argv[i], 255);
91
92 }
93 }
94
95 return TRUE;
96 }
97
98
99
100 /*
101 *
102 * Driver function, controls the traceroute program
103 *
104 */
105 INT Driver(VOID) {
106
107 INT i;
108 INT iHopCount = 1; // hop counter. default max is 30
109 INT iSeqNum = 0; // initialise packet sequence number
110 INT iTTL = 1; // set initial packet TTL to 1
111 BOOL bFoundTarget = FALSE; // Have we reached our destination yet
112 BOOL bAwaitPacket; // indicates whether we have recieved a good packet
113 INT iDecRes; // DecodeResponse return value
114 INT iRecieveReturn; // RecieveReturn return value
115 INT iNameInfoRet; // getnameinfo return value
116 INT iPacketSize = PACKET_SIZE; // packet size
117 WORD wHeaderLen; // header length
118 PECHO_REPLY_HEADER icmphdr;
119
120
121 //temps for getting host name
122 CHAR cHost[256];
123 CHAR cServ[256];
124 CHAR *ip;
125
126 /* setup winsock */
127 WSADATA wsaData;
128
129 /* check for winsock 2 */
130 if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
131 #ifdef DBG
132 _tprintf(_T("WSAStartup failed.\n"));
133 #endif /* DBG */
134 exit(1);
135 }
136
137 SetupTimingMethod();
138
139 /* setup target info */
140 ResolveHostname();
141
142 /* print standard tracing info to screen */
143 _tprintf(_T("\nTracing route to %s [%s]\n"), cHostname, cDestIP);
144 _tprintf(_T("over a maximum of %d hop"), iMaxHops);
145 iMaxHops > 1 ? _tprintf(_T("s:\n\n")) : _tprintf(_T(":\n\n"));
146
147 /* run until we hit either max hops, or we recieve 3 echo replys */
148 while ((iHopCount <= iMaxHops) && (bFoundTarget != TRUE)) {
149 _tprintf(_T("%3d "), iHopCount);
150 /* run 3 pings for each hop */
151 for (i=0; i<3; i++) {
152 if (Setup(iTTL) != TRUE) {
153 #ifdef DBG
154 _tprintf(_T("error in Setup()\n"));
155 #endif /* DBG */
156 WSACleanup();
157 exit(1);
158 }
159 PreparePacket(iPacketSize, iSeqNum);
160 if (SendPacket(iPacketSize) != SOCKET_ERROR) {
161 /* loop until we get a good packet */
162 bAwaitPacket = TRUE;
163 while (bAwaitPacket) {
164 /* Receive replies until we either get a successful
165 * read, or a fatal error occurs. */
166 if ((iRecieveReturn = ReceivePacket(iPacketSize)) < 0) {
167 /* check the sequence number in the packet
168 * if it's bad, complain and wait for another packet
169 * , otherwise break */
170 wHeaderLen = recvpacket.h_len * 4;
171 icmphdr = (ECHO_REPLY_HEADER *)((char*)&recvpacket + wHeaderLen);
172 if (icmphdr->icmpheader.seq != iSeqNum) {
173 _tprintf(_T("bad sequence number!\n"));
174 continue;
175 } else {
176 break;
177 }
178 }
179
180 /* if RecievePacket timed out we don't bother decoding */
181 if (iRecieveReturn != 1) {
182 iDecRes = DecodeResponse(iPacketSize, iSeqNum);
183
184 switch (iDecRes) {
185 case 0 : bAwaitPacket = FALSE; /* time exceeded */
186 break;
187 case 1 : bAwaitPacket = FALSE; /* echo reply */
188 break;
189 case 2 : bAwaitPacket = FALSE; /* destination unreachable */
190 break;
191 #ifdef DBG
192 case -1 :
193 _tprintf(_T("recieved foreign packet\n"));
194 break;
195 case -2 :
196 _tprintf(_T("error in DecodeResponse\n"));
197 break;
198 case -3 :
199 _tprintf(_T("unknown ICMP packet\n"));
200 break;
201 #endif /* DBG */
202 default : break;
203 }
204 } else {
205 /* packet timed out. Don't wait for it again */
206 bAwaitPacket = FALSE;
207 }
208 }
209 }
210
211 iSeqNum++;
212 _tprintf(_T(" "));
213 }
214
215 if(bResolveAddresses) {
216 /* gethostbyaddr() and getnameinfo() are
217 * unimplemented in ROS at present.
218 * Alex has advised he will be implementing gethostbyaddr
219 * but as it's depricieted and getnameinfo is much nicer,
220 * I've used that for the time being for testing in Windows*/
221
222 //ip = inet_addr(inet_ntoa(source.sin_addr));
223 //host = gethostbyaddr((char *)&ip, 4, 0);
224
225 ip = inet_ntoa(source.sin_addr);
226
227 iNameInfoRet = getnameinfo((SOCKADDR *)&source,
228 sizeof(SOCKADDR),
229 cHost,
230 256,
231 cServ,
232 256,
233 NI_NUMERICSERV);
234 if (iNameInfoRet == 0) {
235 /* if IP address resolved to a hostname,
236 * print the IP address after it */
237 if (lstrcmpA(cHost, ip) != 0) {
238 _tprintf(_T("%s [%s]"), cHost, ip);
239 } else {
240 _tprintf(_T("%s"), cHost);
241 }
242 } else {
243 _tprintf(_T("error: %d"), WSAGetLastError());
244 #ifdef DBG
245 _tprintf(_T(" getnameinfo failed: %d"), iNameInfoRet);
246 #endif /* DBG */
247 }
248
249 } else {
250 _tprintf(_T("%s"), inet_ntoa(source.sin_addr));
251 }
252 _tprintf(_T("\n"));
253
254 /* check if we've arrived at the target */
255 if (strcmp(cDestIP, inet_ntoa(source.sin_addr)) == 0) {
256 bFoundTarget = TRUE;
257 } else {
258 iTTL++;
259 iHopCount++;
260 Sleep(500);
261 }
262 }
263 _tprintf(_T("\nTrace complete.\n"));
264 WSACleanup();
265
266 return 0;
267 }
268
269 /*
270 * Establish if performance counters are available and
271 * set up timing figures in relation to processor frequency.
272 * If performance counters are not available, we'll be using
273 * gettickcount, so set the figures to 1
274 *
275 */
276 VOID SetupTimingMethod(VOID)
277 {
278 LARGE_INTEGER PerformanceCounterFrequency;
279
280 /* check if performance counters are available */
281 bUsePerformanceCounter = QueryPerformanceFrequency(&PerformanceCounterFrequency);
282 if (bUsePerformanceCounter) {
283 /* restrict execution to first processor on SMP systems */
284 if (SetThreadAffinityMask(GetCurrentThread(), 1) == 0) {
285 bUsePerformanceCounter = FALSE;
286 }
287
288 TicksPerMs.QuadPart = PerformanceCounterFrequency.QuadPart / 1000;
289 TicksPerUs.QuadPart = PerformanceCounterFrequency.QuadPart / 1000000;
290 }
291
292 if (!bUsePerformanceCounter) {
293 TicksPerMs.QuadPart = 1;
294 TicksPerUs.QuadPart = 1;
295 }
296 }
297
298
299 /*
300 *
301 * Check for a hostname or dotted deciamal for our target.
302 * If we have a hostname, resolve to an IP and store it, else
303 * just store the target IP address. Also set up other key
304 * SOCKADDR_IN members needed for the connection.
305 *
306 */
307 VOID ResolveHostname(VOID)
308 {
309 HOSTENT *hp;
310 ULONG addr;
311
312 memset(&dest, 0, sizeof(dest));
313
314 addr = inet_addr(cHostname);
315 /* if address is not a dotted decimal */
316 if (addr == INADDR_NONE) {
317 hp = gethostbyname(cHostname);
318 if (hp != 0) {
319 memcpy(&dest.sin_addr, hp->h_addr, hp->h_length);
320 //dest.sin_addr = *((struct in_addr *)hp->h_addr);
321 dest.sin_family = hp->h_addrtype;
322 } else {
323 _tprintf(_T("Unable to resolve target system name %s.\n"), cHostname);
324 WSACleanup();
325 exit(1);
326 }
327 } else {
328 dest.sin_addr.s_addr = addr;
329 dest.sin_family = AF_INET;
330 }
331 /* copy destination IP address into a string */
332 strcpy(cDestIP, inet_ntoa(dest.sin_addr));
333 }
334
335
336
337 /*
338 *
339 * Create our socket which will be used for sending and recieving,
340 * Socket Type is raw, Protocol is ICMP. Also set the TTL value which will be
341 * set in the outgoing IP packet.
342 *
343 */
344 INT Setup(INT iTTL)
345 {
346 INT iSockRet;
347
348 /* create raw socket */
349 icmpSock = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, 0, 0, 0);
350 if (icmpSock == INVALID_SOCKET) {
351 _tprintf(_T("Could not create socket : %d.\n"), WSAGetLastError());
352 if (WSAGetLastError() == WSAEACCES) {
353 _tprintf(_T("\n\nYou must be an administrator to run this program!\n\n"));
354 WSACleanup();
355 exit(1);
356 }
357 return FALSE;
358 }
359
360 /* setup for TTL */
361 iSockRet = setsockopt(icmpSock, IPPROTO_IP, IP_TTL, (const char *)&iTTL, sizeof(iTTL));
362 if (iSockRet == SOCKET_ERROR) {
363 _tprintf(_T("TTL setsockopt failed : %d. \n"), WSAGetLastError());
364 return FALSE;
365 }
366
367 return TRUE;
368 }
369
370
371
372 /*
373 * Prepare the ICMP echo request packet for sending.
374 * Calculate the packet checksum
375 *
376 */
377 VOID PreparePacket(INT iPacketSize, INT iSeqNum)
378 {
379 /* assemble ICMP echo request packet */
380 sendpacket.icmpheader.type = ECHO_REQUEST;
381 sendpacket.icmpheader.code = 0;
382 sendpacket.icmpheader.checksum = 0;
383 sendpacket.icmpheader.id = (USHORT)GetCurrentProcessId();
384 sendpacket.icmpheader.seq = iSeqNum;
385
386 /* calculate checksum of packet */
387 sendpacket.icmpheader.checksum = CheckSum((PUSHORT)&sendpacket, sizeof(ICMP_HEADER) + iPacketSize);
388 }
389
390
391
392 /*
393 *
394 * Get the system time and send the ICMP packet to the destination
395 * address.
396 *
397 */
398 INT SendPacket(INT datasize)
399 {
400 INT iSockRet;
401 INT iPacketSize;
402
403 iPacketSize = sizeof(ECHO_REPLY_HEADER) + datasize;
404
405 #ifdef DBG
406 _tprintf(_T("\nsending packet of %d bytes\n"), iPacketSize);
407 #endif /* DBG */
408
409 /* get time packet was sent */
410 lTimeStart = GetTime();
411
412 iSockRet = sendto(icmpSock, //socket
413 (char *)&sendpacket, //buffer
414 iPacketSize, //size of buffer
415 0, //flags
416 (SOCKADDR *)&dest, //destination
417 sizeof(dest)); //address length
418
419 if (iSockRet == SOCKET_ERROR) {
420 if (WSAGetLastError() == WSAEACCES) {
421 _tprintf(_T("\n\nYou must be an administrator to run this program!\n\n"));
422 exit(1);
423 WSACleanup();
424 } else {
425 #ifdef DBG
426 _tprintf(_T("sendto failed %d\n"), WSAGetLastError());
427 #endif /* DBG */
428 return FALSE;
429 }
430 }
431 #ifdef DBG
432 _tprintf(_T("sent %d bytes\n"), iSockRet);
433 #endif /* DBG */
434
435 /* return number of bytes sent */
436 return iSockRet;
437 }
438
439
440
441 /*
442 *
443 * Set up a timeout value and put the socket in a select poll.
444 * Wait until we recieve an IPv4 reply packet in reply to the ICMP
445 * echo request packet and get the time the packet was recieved.
446 * If we don't recieve a packet, do some checking to establish why.
447 *
448 */
449 INT ReceivePacket(INT datasize)
450 {
451 TIMEVAL timeVal;
452 FD_SET readFDS;
453 int iSockRet, iSelRet;
454 int iFromLen;
455 int iPacketSize;
456
457 /* allow for a larger recv buffer to store ICMP TTL
458 * exceed, IP header and orginal ICMP request */
459 iPacketSize = MAX_REC_SIZE + datasize;
460
461 iFromLen = sizeof(source);
462
463 #ifdef DBG
464 _tprintf(_T("receiving packet. Available buffer, %d bytes\n"), iPacketSize);
465 #endif /* DBG */
466
467 /* monitor icmpSock for incomming connections */
468 FD_ZERO(&readFDS);
469 FD_SET(icmpSock, &readFDS);
470
471 /* set timeout values */
472 timeVal.tv_sec = iTimeOut / 1000;
473 timeVal.tv_usec = iTimeOut % 1000;
474
475 iSelRet = select(0, &readFDS, NULL, NULL, &timeVal);
476
477 if ((iSelRet != SOCKET_ERROR) && (iSelRet != 0)) {
478 iSockRet = recvfrom(icmpSock, //socket
479 (char *)&recvpacket, //buffer
480 iPacketSize, //size of buffer
481 0, //flags
482 (SOCKADDR *)&source, //source address
483 &iFromLen); //pointer to address length
484 /* get time packet was recieved */
485 lTimeEnd = GetTime();
486 /* if socket timed out */
487 } else if (iSelRet == 0) {
488 _tprintf(_T(" * "));
489 return 1;
490 } else if (iSelRet == SOCKET_ERROR) {
491 _tprintf(_T("select() failed in sendPacket() %d\n"), WSAGetLastError());
492 return -1;
493 }
494
495
496 if (iSockRet == SOCKET_ERROR) {
497 _tprintf(_T("recvfrom failed: %d\n"), WSAGetLastError());
498 return -2;
499 }
500 #ifdef DBG
501 else {
502 _tprintf(_T("reveived %d bytes\n"), iSockRet);
503 }
504 #endif /* DBG */
505
506 return 0;
507 }
508
509
510
511 /*
512 *
513 * Cast the IPv4 packet to an echo reply and to a TTL exceed.
514 * Check the 'type' field to establish what was recieved, and
515 * ensure the packet is related to the originating process.
516 * It all is well, print the time taken for the round trip.
517 *
518 */
519 INT DecodeResponse(INT iPacketSize, INT iSeqNum)
520 {
521 unsigned short header_len = recvpacket.h_len * 4;
522 /* cast the recieved packet into an ECHO reply and a TTL Exceed so we can check the ID*/
523 ECHO_REPLY_HEADER *IcmpHdr = (ECHO_REPLY_HEADER *)((char*)&recvpacket + header_len);
524 TTL_EXCEED_HEADER *TTLExceedHdr = (TTL_EXCEED_HEADER *)((char *)&recvpacket + header_len);
525
526 /* Make sure the reply is ok */
527 if (iPacketSize < header_len + ICMP_MIN_SIZE) {
528 _tprintf(_T("too few bytes from %s\n"), inet_ntoa(dest.sin_addr));
529 return -2;
530 }
531
532 switch (IcmpHdr->icmpheader.type) {
533 case TTL_EXCEEDED :
534 if (TTLExceedHdr->OrigIcmpHeader.id != (USHORT)GetCurrentProcessId()) {
535 /* FIXME */
536 /* we've picked up a packet not related to this process
537 * probably from another local program. We ignore it */
538 #ifdef DGB
539 _tprintf(_T("header id, process id %d"), TTLExceedHdr->OrigIcmpHeader.id, GetCurrentProcessId());
540 #endif /* DBG */
541 //_tprintf(_T("oops ");
542 return -1;
543 }
544 _tprintf(_T("%3Ld ms"), (lTimeEnd - lTimeStart) / TicksPerMs.QuadPart);
545 return 0;
546 case ECHO_REPLY :
547 if (IcmpHdr->icmpheader.id != (USHORT)GetCurrentProcessId()) {
548 /* FIXME */
549 /* we've picked up a packet not related to this process
550 * probably from another local program. We ignore it */
551 #ifdef DGB
552 _tprintf(_T("\nPicked up wrong packet. icmpheader.id = %d and process id = %d"), IcmpHdr->icmpheader.id, GetCurrentProcessId());
553 #endif /* DBG */
554 //_tprintf(_T("oops ");
555 return -1;
556 }
557 _tprintf(_T("%3Ld ms"), (lTimeEnd - lTimeStart) / TicksPerMs.QuadPart);
558 return 1;
559 case DEST_UNREACHABLE :
560 _tprintf(_T(" * "));
561 return 2;
562 default :
563 /* unknown ICMP packet */
564 return -3;
565 }
566 }
567
568
569 /*
570 *
571 * Get the system time using preformance counters if available,
572 * otherwise fall back to GetTickCount()
573 *
574 */
575
576 LONG GetTime(VOID)
577 {
578 LARGE_INTEGER Time;
579
580 if (bUsePerformanceCounter) {
581 if (QueryPerformanceCounter(&Time) == 0) {
582 Time.u.LowPart = (DWORD)GetTickCount();
583 Time.u.HighPart = 0;
584 return (LONGLONG)Time.u.LowPart;
585 }
586 } else {
587 Time.u.LowPart = (DWORD)GetTickCount();
588 Time.u.HighPart = 0;
589 return (LONGLONG)Time.u.LowPart;
590 }
591 return Time.QuadPart;
592 }
593
594
595 /*
596 *
597 * Calculate packet checksum.
598 *
599 */
600 WORD CheckSum(PUSHORT data, UINT size)
601 {
602 DWORD dwSum = 0;
603
604 while (size > 1) {
605 dwSum += *data++;
606 size -= sizeof(USHORT);
607 }
608
609 if (size)
610 dwSum += *(UCHAR*)data;
611
612 dwSum = (dwSum >> 16) + (dwSum & 0xFFFF);
613 dwSum += (dwSum >> 16);
614
615 return (USHORT)(~dwSum);
616 }
617
618
619 /*
620 *
621 * print program usage to screen
622 *
623 */
624 VOID Usage(VOID)
625 {
626 _tprintf(_T("\nUsage: tracert [-d] [-h maximum_hops] [-j host-list] [-w timeout] target_name\n\n"));
627 _tprintf(_T("Options:\n"));
628 _tprintf(_T(" -d Do not resolve addresses to hostnames.\n"));
629 _tprintf(_T(" -h maximum_hops Maximum number of hops to search for target.\n"));
630 _tprintf(_T(" -j host-list Loose source route along host-list.\n"));
631 _tprintf(_T(" -w timeout Wait timeout milliseconds for each reply.\n\n"));
632
633 /* temp notes to stop user questions until getnameinfo/gethostbyaddr and getsockopt are implemented */
634 _tprintf(_T("NOTES\n-----\n"
635 "- Setting TTL values is not currently supported in ReactOS, so the trace will\n"
636 " jump straight to the destination. This feature will be implemented soon.\n"
637 "- Host info is not currently available in ReactOS and will fail with strange\n"
638 " results. Use -d to force it not to resolve IP's.\n"
639 "- For testing purposes, all should work as normal in a Windows environment\n\n"));
640 }
641
642
643
644 /*
645 *
646 * Program entry point
647 *
648 */
649 int main(int argc, char* argv[])
650 {
651 if (!ParseCmdline(argc, _argv)) return -1;
652
653 Driver();
654
655 return 0;
656 }