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)
23 #define WIN32_LEAN_AND_MEAN
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
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
43 CHAR cHostname
[256]; // target hostname
44 CHAR cDestIP
[18]; // target IP
48 * command line options
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
60 * Parse command line parameters and set any options
63 BOOL
ParseCmdline(int argc
, char* argv
[])
73 for (i
= 1; i
< argc
; i
++) {
74 if (argv
[i
][0] == '-') {
76 case 'd': bResolveAddresses
= FALSE
;
78 case 'h': sscanf(argv
[i
+1], "%d", &iMaxHops
);
80 case 'l': break; /* @unimplemented@ */
81 case 'w': sscanf(argv
[i
+1], "%d", &iTimeOut
);
84 _tprintf(_T("%s is not a valid option.\n"), argv
[i
]);
89 /* copy target address */
90 strncpy(cHostname
, argv
[i
], 255);
102 * Driver function, controls the traceroute program
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
;
121 //temps for getting host name
129 /* check for winsock 2 */
130 if (WSAStartup(MAKEWORD(2, 2), &wsaData
) != 0) {
132 _tprintf(_T("WSAStartup failed.\n"));
139 /* setup target info */
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"));
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
) {
154 _tprintf(_T("error in Setup()\n"));
159 PreparePacket(iPacketSize
, iSeqNum
);
160 if (SendPacket(iPacketSize
) != SOCKET_ERROR
) {
161 /* loop until we get a good packet */
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"));
180 /* if RecievePacket timed out we don't bother decoding */
181 if (iRecieveReturn
!= 1) {
182 iDecRes
= DecodeResponse(iPacketSize
, iSeqNum
);
185 case 0 : bAwaitPacket
= FALSE
; /* time exceeded */
187 case 1 : bAwaitPacket
= FALSE
; /* echo reply */
189 case 2 : bAwaitPacket
= FALSE
; /* destination unreachable */
193 _tprintf(_T("recieved foreign packet\n"));
196 _tprintf(_T("error in DecodeResponse\n"));
199 _tprintf(_T("unknown ICMP packet\n"));
205 /* packet timed out. Don't wait for it again */
206 bAwaitPacket
= FALSE
;
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*/
222 //ip = inet_addr(inet_ntoa(source.sin_addr));
223 //host = gethostbyaddr((char *)&ip, 4, 0);
225 ip
= inet_ntoa(source
.sin_addr
);
227 iNameInfoRet
= getnameinfo((SOCKADDR
*)&source
,
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
);
240 _tprintf(_T("%s"), cHost
);
243 _tprintf(_T("error: %d"), WSAGetLastError());
245 _tprintf(_T(" getnameinfo failed: %d"), iNameInfoRet
);
250 _tprintf(_T("%s"), inet_ntoa(source
.sin_addr
));
254 /* check if we've arrived at the target */
255 if (strcmp(cDestIP
, inet_ntoa(source
.sin_addr
)) == 0) {
263 _tprintf(_T("\nTrace complete.\n"));
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
276 VOID
SetupTimingMethod(VOID
)
278 LARGE_INTEGER PerformanceCounterFrequency
;
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
;
288 TicksPerMs
.QuadPart
= PerformanceCounterFrequency
.QuadPart
/ 1000;
289 TicksPerUs
.QuadPart
= PerformanceCounterFrequency
.QuadPart
/ 1000000;
292 if (!bUsePerformanceCounter
) {
293 TicksPerMs
.QuadPart
= 1;
294 TicksPerUs
.QuadPart
= 1;
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.
307 VOID
ResolveHostname(VOID
)
312 memset(&dest
, 0, sizeof(dest
));
314 addr
= inet_addr(cHostname
);
315 /* if address is not a dotted decimal */
316 if (addr
== INADDR_NONE
) {
317 hp
= gethostbyname(cHostname
);
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
;
323 _tprintf(_T("Unable to resolve target system name %s.\n"), cHostname
);
328 dest
.sin_addr
.s_addr
= addr
;
329 dest
.sin_family
= AF_INET
;
331 /* copy destination IP address into a string */
332 strcpy(cDestIP
, inet_ntoa(dest
.sin_addr
));
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.
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"));
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());
373 * Prepare the ICMP echo request packet for sending.
374 * Calculate the packet checksum
377 VOID
PreparePacket(INT iPacketSize
, INT iSeqNum
)
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
;
386 /* calculate checksum of packet */
387 sendpacket
.icmpheader
.checksum
= CheckSum((PUSHORT
)&sendpacket
, sizeof(ICMP_HEADER
) + iPacketSize
);
394 * Get the system time and send the ICMP packet to the destination
398 INT
SendPacket(INT datasize
)
403 iPacketSize
= sizeof(ECHO_REPLY_HEADER
) + datasize
;
406 _tprintf(_T("\nsending packet of %d bytes\n"), iPacketSize
);
409 /* get time packet was sent */
410 lTimeStart
= GetTime();
412 iSockRet
= sendto(icmpSock
, //socket
413 (char *)&sendpacket
, //buffer
414 iPacketSize
, //size of buffer
416 (SOCKADDR
*)&dest
, //destination
417 sizeof(dest
)); //address length
419 if (iSockRet
== SOCKET_ERROR
) {
420 if (WSAGetLastError() == WSAEACCES
) {
421 _tprintf(_T("\n\nYou must be an administrator to run this program!\n\n"));
426 _tprintf(_T("sendto failed %d\n"), WSAGetLastError());
432 _tprintf(_T("sent %d bytes\n"), iSockRet
);
435 /* return number of bytes sent */
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.
449 INT
ReceivePacket(INT datasize
)
453 int iSockRet
, iSelRet
;
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
;
461 iFromLen
= sizeof(source
);
464 _tprintf(_T("receiving packet. Available buffer, %d bytes\n"), iPacketSize
);
467 /* monitor icmpSock for incomming connections */
469 FD_SET(icmpSock
, &readFDS
);
471 /* set timeout values */
472 timeVal
.tv_sec
= iTimeOut
/ 1000;
473 timeVal
.tv_usec
= iTimeOut
% 1000;
475 iSelRet
= select(0, &readFDS
, NULL
, NULL
, &timeVal
);
477 if ((iSelRet
!= SOCKET_ERROR
) && (iSelRet
!= 0)) {
478 iSockRet
= recvfrom(icmpSock
, //socket
479 (char *)&recvpacket
, //buffer
480 iPacketSize
, //size of buffer
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) {
490 } else if (iSelRet
== SOCKET_ERROR
) {
491 _tprintf(_T("select() failed in sendPacket() %d\n"), WSAGetLastError());
496 if (iSockRet
== SOCKET_ERROR
) {
497 _tprintf(_T("recvfrom failed: %d\n"), WSAGetLastError());
502 _tprintf(_T("reveived %d bytes\n"), iSockRet
);
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.
519 INT
DecodeResponse(INT iPacketSize
, INT iSeqNum
)
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
);
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
));
532 switch (IcmpHdr
->icmpheader
.type
) {
534 if (TTLExceedHdr
->OrigIcmpHeader
.id
!= (USHORT
)GetCurrentProcessId()) {
536 /* we've picked up a packet not related to this process
537 * probably from another local program. We ignore it */
539 _tprintf(_T("header id, process id %d"), TTLExceedHdr
->OrigIcmpHeader
.id
, GetCurrentProcessId());
541 //_tprintf(_T("oops ");
544 _tprintf(_T("%3Ld ms"), (lTimeEnd
- lTimeStart
) / TicksPerMs
.QuadPart
);
547 if (IcmpHdr
->icmpheader
.id
!= (USHORT
)GetCurrentProcessId()) {
549 /* we've picked up a packet not related to this process
550 * probably from another local program. We ignore it */
552 _tprintf(_T("\nPicked up wrong packet. icmpheader.id = %d and process id = %d"), IcmpHdr
->icmpheader
.id
, GetCurrentProcessId());
554 //_tprintf(_T("oops ");
557 _tprintf(_T("%3Ld ms"), (lTimeEnd
- lTimeStart
) / TicksPerMs
.QuadPart
);
559 case DEST_UNREACHABLE
:
563 /* unknown ICMP packet */
571 * Get the system time using preformance counters if available,
572 * otherwise fall back to GetTickCount()
580 if (bUsePerformanceCounter
) {
581 if (QueryPerformanceCounter(&Time
) == 0) {
582 Time
.u
.LowPart
= (DWORD
)GetTickCount();
584 return (LONGLONG
)Time
.u
.LowPart
;
587 Time
.u
.LowPart
= (DWORD
)GetTickCount();
589 return (LONGLONG
)Time
.u
.LowPart
;
591 return Time
.QuadPart
;
597 * Calculate packet checksum.
600 WORD
CheckSum(PUSHORT data
, UINT size
)
606 size
-= sizeof(USHORT
);
610 dwSum
+= *(UCHAR
*)data
;
612 dwSum
= (dwSum
>> 16) + (dwSum
& 0xFFFF);
613 dwSum
+= (dwSum
>> 16);
615 return (USHORT
)(~dwSum
);
621 * print program usage to screen
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"));
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"));
646 * Program entry point
649 int main(int argc
, char* argv
[])
651 if (!ParseCmdline(argc
, _argv
)) return -1;