2 * ReactOS Win32 Applications
3 * Copyright (C) 2005 ReactOS Team
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.
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.
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.
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)
41 #define WIN32_LEAN_AND_MEAN
50 SOCKET icmpSock
; // socket descriptor
51 SOCKADDR_IN source
, dest
; // source and destination address info
52 ECHO_REPLY_HEADER sendpacket
; // ICMP echo packet
53 IPv4_HEADER recvpacket
; // return reveive packet
55 BOOL bUsePerformanceCounter
; // whether to use the high res performance counter
56 LARGE_INTEGER TicksPerMs
; // number of millisecs in relation to proc freq
57 LARGE_INTEGER TicksPerUs
; // number of microsecs in relation to proc freq
58 LONGLONG lTimeStart
; // send packet, timer start
59 LONGLONG lTimeEnd
; // receive packet, timer end
61 CHAR cHostname
[256]; // target hostname
62 CHAR cDestIP
[18]; // target IP
66 * command line options
68 BOOL bResolveAddresses
= TRUE
; // -d MS ping defaults to true.
69 INT iMaxHops
= 30; // -h Max number of hops before trace ends
70 INT iHostList
; // -j @UNIMPLEMENTED@
71 INT iTimeOut
= 2000; // -w time before packet times out
73 /* function definitions */
74 static BOOL
ParseCmdline(int argc
, char* argv
[]);
75 static INT
Driver(void);
76 static INT
Setup(INT ttl
);
77 static VOID
SetupTimingMethod(void);
78 static VOID
ResolveHostname(void);
79 static VOID
PreparePacket(INT packetSize
, INT seqNum
);
80 static INT
SendPacket(INT datasize
);
81 static INT
ReceivePacket(INT datasize
);
82 static INT
DecodeResponse(INT packetSize
, INT seqNum
);
83 static LONG
GetTime(void);
84 static WORD
CheckSum(PUSHORT data
, UINT size
);
85 static VOID
Usage(void);
90 * Parse command line parameters and set any options
93 static BOOL
ParseCmdline(int argc
, char* argv
[])
103 for (i
= 1; i
< argc
; i
++)
105 if (argv
[i
][0] == '-')
109 case 'd': bResolveAddresses
= FALSE
;
111 case 'h': sscanf(argv
[i
+1], "%d", &iMaxHops
);
113 case 'j': break; /* @unimplemented@ */
114 case 'w': sscanf(argv
[i
+1], "%d", &iTimeOut
);
117 _tprintf(_T("%s is not a valid option.\n"), argv
[i
]);
123 /* copy target address */
124 strncpy(cHostname
, argv
[i
], 255);
134 * Driver function, controls the traceroute program
137 static INT
Driver(VOID
)
140 INT iHopCount
= 1; // hop counter. default max is 30
141 INT iSeqNum
= 0; // initialise packet sequence number
142 INT iTTL
= 1; // set initial packet TTL to 1
143 BOOL bFoundTarget
= FALSE
; // Have we reached our destination yet
144 BOOL bAwaitPacket
; // indicates whether we have recieved a good packet
145 INT iDecRes
; // DecodeResponse return value
146 INT iRecieveReturn
; // RecieveReturn return value
147 INT iNameInfoRet
; // getnameinfo return value
148 INT iPacketSize
= PACKET_SIZE
; // packet size
149 WORD wHeaderLen
; // header length
150 PECHO_REPLY_HEADER icmphdr
;
153 //temps for getting host name
161 /* check for winsock 2 */
162 if (WSAStartup(MAKEWORD(2, 2), &wsaData
) != 0)
165 _tprintf(_T("WSAStartup failed.\n"));
170 /* establish what timing method we can use */
173 /* setup target info */
176 /* print standard tracing info to screen */
177 _tprintf(_T("\nTracing route to %s [%s]\n"), cHostname
, cDestIP
);
178 _tprintf(_T("over a maximum of %d hop"), iMaxHops
);
179 iMaxHops
> 1 ? _tprintf(_T("s:\n\n")) : _tprintf(_T(":\n\n"));
181 /* run until we hit either max hops, or we recieve 3 echo replys */
182 while ((iHopCount
<= iMaxHops
) && (bFoundTarget
!= TRUE
))
186 _tprintf(_T("%3d "), iHopCount
);
187 /* run 3 pings for each hop */
190 if (Setup(iTTL
) != TRUE
)
193 _tprintf(_T("error in Setup()\n"));
198 PreparePacket(iPacketSize
, iSeqNum
);
199 if (SendPacket(iPacketSize
) != SOCKET_ERROR
)
201 /* loop until we get a good packet */
205 /* Receive replies until we either get a successful
206 * read, or a fatal error occurs. */
207 if ((iRecieveReturn
= ReceivePacket(iPacketSize
)) < 0)
209 /* check the sequence number in the packet
210 * if it's bad, complain and wait for another packet
211 * , otherwise break */
212 wHeaderLen
= recvpacket
.h_len
* 4;
213 icmphdr
= (ECHO_REPLY_HEADER
*)((char*)&recvpacket
+ wHeaderLen
);
214 if (icmphdr
->icmpheader
.seq
!= iSeqNum
)
216 _tprintf(_T("bad sequence number!\n"));
223 /* if RecievePacket timed out we don't bother decoding */
224 if (iRecieveReturn
!= 1)
226 iDecRes
= DecodeResponse(iPacketSize
, iSeqNum
);
230 case 0 : bAwaitPacket
= FALSE
; /* time exceeded */
232 case 1 : bAwaitPacket
= FALSE
; /* echo reply */
234 case 2 : bAwaitPacket
= FALSE
; /* destination unreachable */
238 _tprintf(_T("recieved foreign packet\n"));
241 _tprintf(_T("error in DecodeResponse\n"));
244 _tprintf(_T("unknown ICMP packet\n"));
251 /* packet timed out. Don't wait for it again */
252 bAwaitPacket
= FALSE
;
260 if(bResolveAddresses
)
262 /* gethostbyaddr() and getnameinfo() are
263 * unimplemented in ROS at present.
264 * Alex has advised he will be implementing getnameinfo.
265 * I've used that for the time being for testing in Windows*/
267 //ip = inet_addr(inet_ntoa(source.sin_addr));
268 //host = gethostbyaddr((char *)&ip, 4, 0);
270 ip
= inet_ntoa(source
.sin_addr
);
272 iNameInfoRet
= getnameinfo((SOCKADDR
*)&source
,
279 if (iNameInfoRet
== 0)
281 /* if IP address resolved to a hostname,
282 * print the IP address after it */
283 if (lstrcmpA(cHost
, ip
) != 0)
284 _tprintf(_T("%s [%s]"), cHost
, ip
);
286 _tprintf(_T("%s"), cHost
);
290 _tprintf(_T("error: %d"), WSAGetLastError());
292 _tprintf(_T(" getnameinfo failed: %d"), iNameInfoRet
);
298 _tprintf(_T("%s"), inet_ntoa(source
.sin_addr
));
302 /* check if we've arrived at the target */
303 if (strcmp(cDestIP
, inet_ntoa(source
.sin_addr
)) == 0)
312 _tprintf(_T("\nTrace complete.\n"));
320 * Establish if performance counters are available and
321 * set up timing figures in relation to processor frequency.
322 * If performance counters are not available, we'll be using
323 * gettickcount, so set the figures to 1
326 static VOID
SetupTimingMethod(VOID
)
328 LARGE_INTEGER PerformanceCounterFrequency
;
330 /* check if performance counters are available */
331 bUsePerformanceCounter
= QueryPerformanceFrequency(&PerformanceCounterFrequency
);
332 if (bUsePerformanceCounter
)
334 /* restrict execution to first processor on SMP systems */
335 if (SetThreadAffinityMask(GetCurrentThread(), 1) == 0)
336 bUsePerformanceCounter
= FALSE
;
338 TicksPerMs
.QuadPart
= PerformanceCounterFrequency
.QuadPart
/ 1000;
339 TicksPerUs
.QuadPart
= PerformanceCounterFrequency
.QuadPart
/ 1000000;
342 if (!bUsePerformanceCounter
)
344 TicksPerMs
.QuadPart
= 1;
345 TicksPerUs
.QuadPart
= 1;
352 * Check for a hostname or dotted deciamal for our target.
353 * If we have a hostname, resolve to an IP and store it, else
354 * just store the target IP address. Also set up other key
355 * SOCKADDR_IN members needed for the connection.
358 static VOID
ResolveHostname(VOID
)
363 memset(&dest
, 0, sizeof(dest
));
365 addr
= inet_addr(cHostname
);
366 /* if address is not a dotted decimal */
367 if (addr
== INADDR_NONE
)
369 hp
= gethostbyname(cHostname
);
372 memcpy(&dest
.sin_addr
, hp
->h_addr
, hp
->h_length
);
373 //dest.sin_addr = *((struct in_addr *)hp->h_addr);
374 dest
.sin_family
= hp
->h_addrtype
;
378 _tprintf(_T("Unable to resolve target system name %s.\n"), cHostname
);
385 dest
.sin_addr
.s_addr
= addr
;
386 dest
.sin_family
= AF_INET
;
388 /* copy destination IP address into a string */
389 strcpy(cDestIP
, inet_ntoa(dest
.sin_addr
));
396 * Create our socket which will be used for sending and recieving,
397 * Socket Type is raw, Protocol is ICMP. Also set the TTL value which will be
398 * set in the outgoing IP packet.
401 static INT
Setup(INT iTTL
)
405 /* create raw socket */
406 icmpSock
= WSASocket(AF_INET
, SOCK_RAW
, IPPROTO_ICMP
, 0, 0, 0);
407 if (icmpSock
== INVALID_SOCKET
)
409 _tprintf(_T("Could not create socket : %d.\n"), WSAGetLastError());
410 if (WSAGetLastError() == WSAEACCES
)
412 _tprintf(_T("\n\nYou must be an administrator to run this program!\n\n"));
420 iSockRet
= setsockopt(icmpSock
, IPPROTO_IP
, IP_TTL
, (const char *)&iTTL
, sizeof(iTTL
));
421 if (iSockRet
== SOCKET_ERROR
)
423 _tprintf(_T("TTL setsockopt failed : %d. \n"), WSAGetLastError());
433 * Prepare the ICMP echo request packet for sending.
434 * Calculate the packet checksum
437 static VOID
PreparePacket(INT iPacketSize
, INT iSeqNum
)
439 /* assemble ICMP echo request packet */
440 sendpacket
.icmpheader
.type
= ECHO_REQUEST
;
441 sendpacket
.icmpheader
.code
= 0;
442 sendpacket
.icmpheader
.checksum
= 0;
443 sendpacket
.icmpheader
.id
= (USHORT
)GetCurrentProcessId();
444 sendpacket
.icmpheader
.seq
= iSeqNum
;
446 /* calculate checksum of packet */
447 sendpacket
.icmpheader
.checksum
= CheckSum((PUSHORT
)&sendpacket
, sizeof(ICMP_HEADER
) + iPacketSize
);
454 * Get the system time and send the ICMP packet to the destination
458 static INT
SendPacket(INT datasize
)
463 iPacketSize
= sizeof(ECHO_REPLY_HEADER
) + datasize
;
466 _tprintf(_T("\nsending packet of %d bytes\n"), iPacketSize
);
469 /* get time packet was sent */
470 lTimeStart
= GetTime();
472 iSockRet
= sendto(icmpSock
, //socket
473 (char *)&sendpacket
, //buffer
474 iPacketSize
, //size of buffer
476 (SOCKADDR
*)&dest
, //destination
477 sizeof(dest
)); //address length
479 if (iSockRet
== SOCKET_ERROR
)
481 if (WSAGetLastError() == WSAEACCES
)
483 _tprintf(_T("\n\nYou must be an administrator to run this program!\n\n"));
490 _tprintf(_T("sendto failed %d\n"), WSAGetLastError());
496 _tprintf(_T("sent %d bytes\n"), iSockRet
);
499 /* return number of bytes sent */
507 * Set up a timeout value and put the socket in a select poll.
508 * Wait until we recieve an IPv4 reply packet in reply to the ICMP
509 * echo request packet and get the time the packet was recieved.
510 * If we don't recieve a packet, do some checking to establish why.
513 static INT
ReceivePacket(INT datasize
)
517 int iSockRet
= 0, iSelRet
;
521 /* allow for a larger recv buffer to store ICMP TTL
522 * exceed, IP header and orginal ICMP request */
523 iPacketSize
= MAX_REC_SIZE
+ datasize
;
525 iFromLen
= sizeof(source
);
528 _tprintf(_T("receiving packet. Available buffer, %d bytes\n"), iPacketSize
);
531 /* monitor icmpSock for incomming connections */
533 FD_SET(icmpSock
, &readFDS
);
535 /* set timeout values */
536 timeVal
.tv_sec
= iTimeOut
/ 1000;
537 timeVal
.tv_usec
= iTimeOut
% 1000;
539 iSelRet
= select(0, &readFDS
, NULL
, NULL
, &timeVal
);
541 if ((iSelRet
!= SOCKET_ERROR
) && (iSelRet
!= 0))
543 iSockRet
= recvfrom(icmpSock
, //socket
544 (char *)&recvpacket
, //buffer
545 iPacketSize
, //size of buffer
547 (SOCKADDR
*)&source
, //source address
548 &iFromLen
); //pointer to address length
549 /* get time packet was recieved */
550 lTimeEnd
= GetTime();
551 /* if socket timed out */
553 else if (iSelRet
== 0)
558 else if (iSelRet
== SOCKET_ERROR
)
560 _tprintf(_T("select() failed in sendPacket() %d\n"), WSAGetLastError());
565 if (iSockRet
== SOCKET_ERROR
)
567 _tprintf(_T("recvfrom failed: %d\n"), WSAGetLastError());
572 _tprintf(_T("reveived %d bytes\n"), iSockRet
);
582 * Cast the IPv4 packet to an echo reply and to a TTL exceed.
583 * Check the 'type' field to establish what was recieved, and
584 * ensure the packet is related to the originating process.
585 * It all is well, print the time taken for the round trip.
588 static INT
DecodeResponse(INT iPacketSize
, INT iSeqNum
)
590 unsigned short header_len
= recvpacket
.h_len
* 4;
591 /* cast the recieved packet into an ECHO reply and a TTL Exceed so we can check the ID*/
592 ECHO_REPLY_HEADER
*IcmpHdr
= (ECHO_REPLY_HEADER
*)((char*)&recvpacket
+ header_len
);
593 TTL_EXCEED_HEADER
*TTLExceedHdr
= (TTL_EXCEED_HEADER
*)((char *)&recvpacket
+ header_len
);
595 /* Make sure the reply is ok */
596 if (iPacketSize
< header_len
+ ICMP_MIN_SIZE
)
598 _tprintf(_T("too few bytes from %s\n"), inet_ntoa(dest
.sin_addr
));
602 switch (IcmpHdr
->icmpheader
.type
)
605 if (TTLExceedHdr
->OrigIcmpHeader
.id
!= (USHORT
)GetCurrentProcessId())
608 /* we've picked up a packet not related to this process
609 * probably from another local program. We ignore it */
611 _tprintf(_T("header id, process id %d"), TTLExceedHdr
->OrigIcmpHeader
.id
, GetCurrentProcessId());
613 //_tprintf(_T("oops ");
616 _tprintf(_T("%3Ld ms"), (lTimeEnd
- lTimeStart
) / TicksPerMs
.QuadPart
);
619 if (IcmpHdr
->icmpheader
.id
!= (USHORT
)GetCurrentProcessId())
622 /* we've picked up a packet not related to this process
623 * probably from another local program. We ignore it */
625 _tprintf(_T("\nPicked up wrong packet. icmpheader.id = %d and process id = %d"), IcmpHdr
->icmpheader
.id
, GetCurrentProcessId());
627 //_tprintf(_T("oops ");
630 _tprintf(_T("%3Ld ms"), (lTimeEnd
- lTimeStart
) / TicksPerMs
.QuadPart
);
632 case DEST_UNREACHABLE
:
636 /* unknown ICMP packet */
644 * Get the system time using preformance counters if available,
645 * otherwise fall back to GetTickCount()
649 static LONG
GetTime(VOID
)
653 if (bUsePerformanceCounter
)
655 if (QueryPerformanceCounter(&Time
) == 0)
657 Time
.u
.LowPart
= (DWORD
)GetTickCount();
659 return (LONGLONG
)Time
.u
.LowPart
;
664 Time
.u
.LowPart
= (DWORD
)GetTickCount();
666 return (LONGLONG
)Time
.u
.LowPart
;
668 return Time
.QuadPart
;
674 * Calculate packet checksum.
677 static WORD
CheckSum(PUSHORT data
, UINT size
)
684 size
-= sizeof(USHORT
);
688 dwSum
+= *(UCHAR
*)data
;
690 dwSum
= (dwSum
>> 16) + (dwSum
& 0xFFFF);
691 dwSum
+= (dwSum
>> 16);
693 return (USHORT
)(~dwSum
);
699 * print program usage to screen
702 static VOID
Usage(VOID
)
704 _tprintf(_T("\nUsage: tracert [-d] [-h maximum_hops] [-j host-list] [-w timeout] target_name\n\n"
706 " -d Do not resolve addresses to hostnames.\n"
707 " -h maximum_hops Maximum number of hops to search for target.\n"
708 " -j host-list Loose source route along host-list.\n"
709 " -w timeout Wait timeout milliseconds for each reply.\n\n"));
711 /* temp notes to stop user questions until getnameinfo/gethostbyaddr and getsockopt are implemented */
712 _tprintf(_T("NOTES\n-----\n"
713 "- Setting TTL values is not currently supported in ReactOS, so the trace will\n"
714 " jump straight to the destination. This feature will be implemented soon.\n"
715 "- Host info is not currently available in ReactOS and will fail with strange\n"
716 " results. Use -d to force it not to resolve IP's.\n"
717 "- For testing purposes, all should work as normal in a Windows environment\n\n"));
724 * Program entry point
727 int main(int argc
, char* argv
[])
729 if (!ParseCmdline(argc
, _argv
)) return -1;