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