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)
40 #define WIN32_LEAN_AND_MEAN
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
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
60 CHAR cHostname
[256]; // target hostname
61 CHAR cDestIP
[18]; // target IP
65 * command line options
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
75 * Parse command line parameters and set any options
78 static BOOL
ParseCmdline(int argc
, char* argv
[])
88 for (i
= 1; i
< argc
; i
++)
90 if (argv
[i
][0] == '-')
94 case 'd': bResolveAddresses
= FALSE
;
96 case 'h': sscanf(argv
[i
+1], "%d", &iMaxHops
);
98 case 'j': break; /* @unimplemented@ */
99 case 'w': sscanf(argv
[i
+1], "%d", &iTimeOut
);
102 _tprintf(_T("%s is not a valid option.\n"), argv
[i
]);
108 /* copy target address */
109 strncpy(cHostname
, argv
[i
], 255);
119 * Driver function, controls the traceroute program
122 static INT
Driver(VOID
)
125 INT iHopCount
= 1; // hop counter. default max is 30
126 USHORT iSeqNum
= 0; // initialise packet sequence number
127 INT iTTL
= 1; // set initial packet TTL to 1
128 BOOL bFoundTarget
= FALSE
; // Have we reached our destination yet
129 BOOL bAwaitPacket
; // indicates whether we have recieved a good packet
130 INT iDecRes
; // DecodeResponse return value
131 INT iRecieveReturn
; // RecieveReturn return value
132 INT iNameInfoRet
; // getnameinfo return value
133 INT iPacketSize
= PACKET_SIZE
; // packet size
134 WORD wHeaderLen
; // header length
135 PECHO_REPLY_HEADER icmphdr
;
138 //temps for getting host name
146 /* check for winsock 2 */
147 if (WSAStartup(MAKEWORD(2, 2), &wsaData
) != 0)
150 _tprintf(_T("WSAStartup failed.\n"));
155 /* establish what timing method we can use */
158 /* setup target info */
161 /* print standard tracing info to screen */
162 _tprintf(_T("\nTracing route to %s [%s]\n"), cHostname
, cDestIP
);
163 _tprintf(_T("over a maximum of %d hop"), iMaxHops
);
164 iMaxHops
> 1 ? _tprintf(_T("s:\n\n")) : _tprintf(_T(":\n\n"));
166 /* run until we hit either max hops, or we recieve 3 echo replys */
167 while ((iHopCount
<= iMaxHops
) && (bFoundTarget
!= TRUE
))
171 _tprintf(_T("%3d "), iHopCount
);
172 /* run 3 pings for each hop */
175 if (Setup(iTTL
) != TRUE
)
178 _tprintf(_T("error in Setup()\n"));
183 PreparePacket(iPacketSize
, iSeqNum
);
184 if (SendPacket(iPacketSize
) != SOCKET_ERROR
)
186 /* loop until we get a good packet */
190 /* Receive replies until we either get a successful
191 * read, or a fatal error occurs. */
192 if ((iRecieveReturn
= ReceivePacket(iPacketSize
)) < 0)
194 /* check the sequence number in the packet
195 * if it's bad, complain and wait for another packet
196 * , otherwise break */
197 wHeaderLen
= recvpacket
.h_len
* 4;
198 icmphdr
= (ECHO_REPLY_HEADER
*)((char*)&recvpacket
+ wHeaderLen
);
199 if (icmphdr
->icmpheader
.seq
!= iSeqNum
)
201 _tprintf(_T("bad sequence number!\n"));
208 /* if RecievePacket timed out we don't bother decoding */
209 if (iRecieveReturn
!= 1)
211 iDecRes
= DecodeResponse(iPacketSize
, iSeqNum
);
215 case 0 : bAwaitPacket
= FALSE
; /* time exceeded */
217 case 1 : bAwaitPacket
= FALSE
; /* echo reply */
219 case 2 : bAwaitPacket
= FALSE
; /* destination unreachable */
223 _tprintf(_T("recieved foreign packet\n"));
226 _tprintf(_T("error in DecodeResponse\n"));
229 _tprintf(_T("unknown ICMP packet\n"));
236 /* packet timed out. Don't wait for it again */
237 bAwaitPacket
= FALSE
;
245 if(bResolveAddresses
)
247 /* gethostbyaddr() and getnameinfo() are
248 * unimplemented in ROS at present.
249 * Alex has advised he will be implementing getnameinfo.
250 * I've used that for the time being for testing in Windows*/
252 //ip = inet_addr(inet_ntoa(source.sin_addr));
253 //host = gethostbyaddr((char *)&ip, 4, 0);
255 ip
= inet_ntoa(source
.sin_addr
);
257 iNameInfoRet
= getnameinfo((SOCKADDR
*)&source
,
264 if (iNameInfoRet
== 0)
266 /* if IP address resolved to a hostname,
267 * print the IP address after it */
268 if (lstrcmpA(cHost
, ip
) != 0)
269 _tprintf(_T("%s [%s]"), cHost
, ip
);
271 _tprintf(_T("%s"), cHost
);
275 _tprintf(_T("error: %d"), WSAGetLastError());
277 _tprintf(_T(" getnameinfo failed: %d"), iNameInfoRet
);
283 _tprintf(_T("%s"), inet_ntoa(source
.sin_addr
));
287 /* check if we've arrived at the target */
288 if (strcmp(cDestIP
, inet_ntoa(source
.sin_addr
)) == 0)
297 _tprintf(_T("\nTrace complete.\n"));
305 * Establish if performance counters are available and
306 * set up timing figures in relation to processor frequency.
307 * If performance counters are not available, we'll be using
308 * gettickcount, so set the figures to 1
311 static VOID
SetupTimingMethod(VOID
)
313 LARGE_INTEGER PerformanceCounterFrequency
;
315 /* check if performance counters are available */
316 bUsePerformanceCounter
= QueryPerformanceFrequency(&PerformanceCounterFrequency
);
317 if (bUsePerformanceCounter
)
319 /* restrict execution to first processor on SMP systems */
320 if (SetThreadAffinityMask(GetCurrentThread(), 1) == 0)
321 bUsePerformanceCounter
= FALSE
;
323 TicksPerMs
.QuadPart
= PerformanceCounterFrequency
.QuadPart
/ 1000;
324 TicksPerUs
.QuadPart
= PerformanceCounterFrequency
.QuadPart
/ 1000000;
327 if (!bUsePerformanceCounter
)
329 TicksPerMs
.QuadPart
= 1;
330 TicksPerUs
.QuadPart
= 1;
337 * Check for a hostname or dotted deciamal for our target.
338 * If we have a hostname, resolve to an IP and store it, else
339 * just store the target IP address. Also set up other key
340 * SOCKADDR_IN members needed for the connection.
343 static VOID
ResolveHostname(VOID
)
348 memset(&dest
, 0, sizeof(dest
));
350 addr
= inet_addr(cHostname
);
351 /* if address is not a dotted decimal */
352 if (addr
== INADDR_NONE
)
354 hp
= gethostbyname(cHostname
);
357 memcpy(&dest
.sin_addr
, hp
->h_addr
, hp
->h_length
);
358 //dest.sin_addr = *((struct in_addr *)hp->h_addr);
359 dest
.sin_family
= hp
->h_addrtype
;
363 _tprintf(_T("Unable to resolve target system name %s.\n"), cHostname
);
370 dest
.sin_addr
.s_addr
= addr
;
371 dest
.sin_family
= AF_INET
;
373 /* copy destination IP address into a string */
374 strcpy(cDestIP
, inet_ntoa(dest
.sin_addr
));
381 * Create our socket which will be used for sending and recieving,
382 * Socket Type is raw, Protocol is ICMP. Also set the TTL value which will be
383 * set in the outgoing IP packet.
386 static INT
Setup(INT iTTL
)
390 /* create raw socket */
391 icmpSock
= WSASocket(AF_INET
, SOCK_RAW
, IPPROTO_ICMP
, 0, 0, 0);
392 if (icmpSock
== INVALID_SOCKET
)
394 _tprintf(_T("Could not create socket : %d.\n"), WSAGetLastError());
395 if (WSAGetLastError() == WSAEACCES
)
397 _tprintf(_T("\n\nYou must be an administrator to run this program!\n\n"));
405 iSockRet
= setsockopt(icmpSock
, IPPROTO_IP
, IP_TTL
, (const char *)&iTTL
, sizeof(iTTL
));
406 if (iSockRet
== SOCKET_ERROR
)
408 _tprintf(_T("TTL setsockopt failed : %d. \n"), WSAGetLastError());
418 * Prepare the ICMP echo request packet for sending.
419 * Calculate the packet checksum
422 static VOID
PreparePacket(INT iPacketSize
, USHORT iSeqNum
)
424 /* assemble ICMP echo request packet */
425 sendpacket
.icmpheader
.type
= ECHO_REQUEST
;
426 sendpacket
.icmpheader
.code
= 0;
427 sendpacket
.icmpheader
.checksum
= 0;
428 sendpacket
.icmpheader
.id
= (USHORT
)GetCurrentProcessId();
429 sendpacket
.icmpheader
.seq
= iSeqNum
;
431 /* calculate checksum of packet */
432 sendpacket
.icmpheader
.checksum
= CheckSum((PUSHORT
)&sendpacket
, sizeof(ICMP_HEADER
) + iPacketSize
);
439 * Get the system time and send the ICMP packet to the destination
443 static INT
SendPacket(INT datasize
)
448 iPacketSize
= sizeof(ECHO_REPLY_HEADER
) + datasize
;
451 _tprintf(_T("\nsending packet of %d bytes\n"), iPacketSize
);
454 /* get time packet was sent */
455 lTimeStart
= GetTime();
457 iSockRet
= sendto(icmpSock
, //socket
458 (char *)&sendpacket
, //buffer
459 iPacketSize
, //size of buffer
461 (SOCKADDR
*)&dest
, //destination
462 sizeof(dest
)); //address length
464 if (iSockRet
== SOCKET_ERROR
)
466 if (WSAGetLastError() == WSAEACCES
)
468 _tprintf(_T("\n\nYou must be an administrator to run this program!\n\n"));
475 _tprintf(_T("sendto failed %d\n"), WSAGetLastError());
481 _tprintf(_T("sent %d bytes\n"), iSockRet
);
484 /* return number of bytes sent */
492 * Set up a timeout value and put the socket in a select poll.
493 * Wait until we recieve an IPv4 reply packet in reply to the ICMP
494 * echo request packet and get the time the packet was recieved.
495 * If we don't recieve a packet, do some checking to establish why.
498 static INT
ReceivePacket(INT datasize
)
502 int iSockRet
= 0, iSelRet
;
506 /* allow for a larger recv buffer to store ICMP TTL
507 * exceed, IP header and orginal ICMP request */
508 iPacketSize
= MAX_REC_SIZE
+ datasize
;
510 iFromLen
= sizeof(source
);
513 _tprintf(_T("receiving packet. Available buffer, %d bytes\n"), iPacketSize
);
516 /* monitor icmpSock for incomming connections */
518 FD_SET(icmpSock
, &readFDS
);
520 /* set timeout values */
521 timeVal
.tv_sec
= iTimeOut
/ 1000;
522 timeVal
.tv_usec
= iTimeOut
% 1000;
524 iSelRet
= select(0, &readFDS
, NULL
, NULL
, &timeVal
);
526 if ((iSelRet
!= SOCKET_ERROR
) && (iSelRet
!= 0))
528 iSockRet
= recvfrom(icmpSock
, //socket
529 (char *)&recvpacket
, //buffer
530 iPacketSize
, //size of buffer
532 (SOCKADDR
*)&source
, //source address
533 &iFromLen
); //pointer to address length
534 /* get time packet was recieved */
535 lTimeEnd
= GetTime();
536 /* if socket timed out */
538 else if (iSelRet
== 0)
543 else if (iSelRet
== SOCKET_ERROR
)
545 _tprintf(_T("select() failed in sendPacket() %d\n"), WSAGetLastError());
550 if (iSockRet
== SOCKET_ERROR
)
552 _tprintf(_T("recvfrom failed: %d\n"), WSAGetLastError());
557 _tprintf(_T("reveived %d bytes\n"), iSockRet
);
567 * Cast the IPv4 packet to an echo reply and to a TTL exceed.
568 * Check the 'type' field to establish what was recieved, and
569 * ensure the packet is related to the originating process.
570 * It all is well, print the time taken for the round trip.
573 static INT
DecodeResponse(INT iPacketSize
, USHORT iSeqNum
)
575 unsigned short header_len
= recvpacket
.h_len
* 4;
576 /* cast the recieved packet into an ECHO reply and a TTL Exceed so we can check the ID*/
577 ECHO_REPLY_HEADER
*IcmpHdr
= (ECHO_REPLY_HEADER
*)((char*)&recvpacket
+ header_len
);
578 TTL_EXCEED_HEADER
*TTLExceedHdr
= (TTL_EXCEED_HEADER
*)((char *)&recvpacket
+ header_len
);
580 /* Make sure the reply is ok */
581 if (iPacketSize
< header_len
+ ICMP_MIN_SIZE
)
583 _tprintf(_T("too few bytes from %s\n"), inet_ntoa(dest
.sin_addr
));
587 switch (IcmpHdr
->icmpheader
.type
)
590 if (TTLExceedHdr
->OrigIcmpHeader
.id
!= (USHORT
)GetCurrentProcessId())
593 /* we've picked up a packet not related to this process
594 * probably from another local program. We ignore it */
596 _tprintf(_T("header id, process id %d"), TTLExceedHdr
->OrigIcmpHeader
.id
, GetCurrentProcessId());
598 //_tprintf(_T("oops ");
601 _tprintf(_T("%3Ld ms"), (lTimeEnd
- lTimeStart
) / TicksPerMs
.QuadPart
);
604 if (IcmpHdr
->icmpheader
.id
!= (USHORT
)GetCurrentProcessId())
607 /* we've picked up a packet not related to this process
608 * probably from another local program. We ignore it */
610 _tprintf(_T("\nPicked up wrong packet. icmpheader.id = %d and process id = %d"), IcmpHdr
->icmpheader
.id
, GetCurrentProcessId());
612 //_tprintf(_T("oops ");
615 _tprintf(_T("%3Ld ms"), (lTimeEnd
- lTimeStart
) / TicksPerMs
.QuadPart
);
617 case DEST_UNREACHABLE
:
621 /* unknown ICMP packet */
629 * Get the system time using preformance counters if available,
630 * otherwise fall back to GetTickCount()
634 static LONGLONG
GetTime(VOID
)
638 if (bUsePerformanceCounter
)
640 if (QueryPerformanceCounter(&Time
) == 0)
642 Time
.u
.LowPart
= (DWORD
)GetTickCount();
644 return (LONGLONG
)Time
.u
.LowPart
;
649 Time
.u
.LowPart
= (DWORD
)GetTickCount();
651 return (LONGLONG
)Time
.u
.LowPart
;
653 return Time
.QuadPart
;
659 * Calculate packet checksum.
662 static WORD
CheckSum(PUSHORT data
, UINT size
)
669 size
-= sizeof(USHORT
);
673 dwSum
+= *(UCHAR
*)data
;
675 dwSum
= (dwSum
>> 16) + (dwSum
& 0xFFFF);
676 dwSum
+= (dwSum
>> 16);
678 return (USHORT
)(~dwSum
);
684 * print program usage to screen
687 static VOID
Usage(VOID
)
689 _tprintf(_T("\nUsage: tracert [-d] [-h maximum_hops] [-j host-list] [-w timeout] target_name\n\n"
691 " -d Do not resolve addresses to hostnames.\n"
692 " -h maximum_hops Maximum number of hops to search for target.\n"
693 " -j host-list Loose source route along host-list.\n"
694 " -w timeout Wait timeout milliseconds for each reply.\n\n"));
696 /* temp notes to stop user questions until getnameinfo/gethostbyaddr and getsockopt are implemented */
697 _tprintf(_T("NOTES\n-----\n"
698 "- Setting TTL values is not currently supported in ReactOS, so the trace will\n"
699 " jump straight to the destination. This feature will be implemented soon.\n"
700 "- Host info is not currently available in ReactOS and will fail with strange\n"
701 " results. Use -d to force it not to resolve IP's.\n"
702 "- For testing purposes, all should work as normal in a Windows environment\n\n"));
709 * Program entry point
712 int main(int argc
, char* argv
[])
714 if (!ParseCmdline(argc
, argv
)) return -1;