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
78 * Parse command line parameters and set any options
81 BOOL
ParseCmdline(int argc
, char* argv
[])
91 for (i
= 1; i
< argc
; i
++)
93 if (argv
[i
][0] == '-')
97 case 'd': bResolveAddresses
= FALSE
;
99 case 'h': sscanf(argv
[i
+1], "%d", &iMaxHops
);
101 case 'j': break; /* @unimplemented@ */
102 case 'w': sscanf(argv
[i
+1], "%d", &iTimeOut
);
105 _tprintf(_T("%s is not a valid option.\n"), argv
[i
]);
111 /* copy target address */
112 strncpy(cHostname
, argv
[i
], 255);
122 * Driver function, controls the traceroute program
128 INT iHopCount
= 1; // hop counter. default max is 30
129 INT iSeqNum
= 0; // initialise packet sequence number
130 INT iTTL
= 1; // set initial packet TTL to 1
131 BOOL bFoundTarget
= FALSE
; // Have we reached our destination yet
132 BOOL bAwaitPacket
; // indicates whether we have recieved a good packet
133 INT iDecRes
; // DecodeResponse return value
134 INT iRecieveReturn
; // RecieveReturn return value
135 INT iNameInfoRet
; // getnameinfo return value
136 INT iPacketSize
= PACKET_SIZE
; // packet size
137 WORD wHeaderLen
; // header length
138 PECHO_REPLY_HEADER icmphdr
;
141 //temps for getting host name
149 /* check for winsock 2 */
150 if (WSAStartup(MAKEWORD(2, 2), &wsaData
) != 0)
153 _tprintf(_T("WSAStartup failed.\n"));
158 /* establish what timing method we can use */
161 /* setup target info */
164 /* print standard tracing info to screen */
165 _tprintf(_T("\nTracing route to %s [%s]\n"), cHostname
, cDestIP
);
166 _tprintf(_T("over a maximum of %d hop"), iMaxHops
);
167 iMaxHops
> 1 ? _tprintf(_T("s:\n\n")) : _tprintf(_T(":\n\n"));
169 /* run until we hit either max hops, or we recieve 3 echo replys */
170 while ((iHopCount
<= iMaxHops
) && (bFoundTarget
!= TRUE
))
174 _tprintf(_T("%3d "), iHopCount
);
175 /* run 3 pings for each hop */
178 if (Setup(iTTL
) != TRUE
)
181 _tprintf(_T("error in Setup()\n"));
186 PreparePacket(iPacketSize
, iSeqNum
);
187 if (SendPacket(iPacketSize
) != SOCKET_ERROR
)
189 /* loop until we get a good packet */
193 /* Receive replies until we either get a successful
194 * read, or a fatal error occurs. */
195 if ((iRecieveReturn
= ReceivePacket(iPacketSize
)) < 0)
197 /* check the sequence number in the packet
198 * if it's bad, complain and wait for another packet
199 * , otherwise break */
200 wHeaderLen
= recvpacket
.h_len
* 4;
201 icmphdr
= (ECHO_REPLY_HEADER
*)((char*)&recvpacket
+ wHeaderLen
);
202 if (icmphdr
->icmpheader
.seq
!= iSeqNum
)
204 _tprintf(_T("bad sequence number!\n"));
211 /* if RecievePacket timed out we don't bother decoding */
212 if (iRecieveReturn
!= 1)
214 iDecRes
= DecodeResponse(iPacketSize
, iSeqNum
);
218 case 0 : bAwaitPacket
= FALSE
; /* time exceeded */
220 case 1 : bAwaitPacket
= FALSE
; /* echo reply */
222 case 2 : bAwaitPacket
= FALSE
; /* destination unreachable */
226 _tprintf(_T("recieved foreign packet\n"));
229 _tprintf(_T("error in DecodeResponse\n"));
232 _tprintf(_T("unknown ICMP packet\n"));
239 /* packet timed out. Don't wait for it again */
240 bAwaitPacket
= FALSE
;
248 if(bResolveAddresses
)
250 /* gethostbyaddr() and getnameinfo() are
251 * unimplemented in ROS at present.
252 * Alex has advised he will be implementing getnameinfo.
253 * I've used that for the time being for testing in Windows*/
255 //ip = inet_addr(inet_ntoa(source.sin_addr));
256 //host = gethostbyaddr((char *)&ip, 4, 0);
258 ip
= inet_ntoa(source
.sin_addr
);
260 iNameInfoRet
= getnameinfo((SOCKADDR
*)&source
,
267 if (iNameInfoRet
== 0)
269 /* if IP address resolved to a hostname,
270 * print the IP address after it */
271 if (lstrcmpA(cHost
, ip
) != 0)
272 _tprintf(_T("%s [%s]"), cHost
, ip
);
274 _tprintf(_T("%s"), cHost
);
278 _tprintf(_T("error: %d"), WSAGetLastError());
280 _tprintf(_T(" getnameinfo failed: %d"), iNameInfoRet
);
286 _tprintf(_T("%s"), inet_ntoa(source
.sin_addr
));
290 /* check if we've arrived at the target */
291 if (strcmp(cDestIP
, inet_ntoa(source
.sin_addr
)) == 0)
300 _tprintf(_T("\nTrace complete.\n"));
308 * Establish if performance counters are available and
309 * set up timing figures in relation to processor frequency.
310 * If performance counters are not available, we'll be using
311 * gettickcount, so set the figures to 1
314 VOID
SetupTimingMethod(VOID
)
316 LARGE_INTEGER PerformanceCounterFrequency
;
318 /* check if performance counters are available */
319 bUsePerformanceCounter
= QueryPerformanceFrequency(&PerformanceCounterFrequency
);
320 if (bUsePerformanceCounter
)
322 /* restrict execution to first processor on SMP systems */
323 if (SetThreadAffinityMask(GetCurrentThread(), 1) == 0)
324 bUsePerformanceCounter
= FALSE
;
326 TicksPerMs
.QuadPart
= PerformanceCounterFrequency
.QuadPart
/ 1000;
327 TicksPerUs
.QuadPart
= PerformanceCounterFrequency
.QuadPart
/ 1000000;
330 if (!bUsePerformanceCounter
)
332 TicksPerMs
.QuadPart
= 1;
333 TicksPerUs
.QuadPart
= 1;
340 * Check for a hostname or dotted deciamal for our target.
341 * If we have a hostname, resolve to an IP and store it, else
342 * just store the target IP address. Also set up other key
343 * SOCKADDR_IN members needed for the connection.
346 VOID
ResolveHostname(VOID
)
351 memset(&dest
, 0, sizeof(dest
));
353 addr
= inet_addr(cHostname
);
354 /* if address is not a dotted decimal */
355 if (addr
== INADDR_NONE
)
357 hp
= gethostbyname(cHostname
);
360 memcpy(&dest
.sin_addr
, hp
->h_addr
, hp
->h_length
);
361 //dest.sin_addr = *((struct in_addr *)hp->h_addr);
362 dest
.sin_family
= hp
->h_addrtype
;
366 _tprintf(_T("Unable to resolve target system name %s.\n"), cHostname
);
373 dest
.sin_addr
.s_addr
= addr
;
374 dest
.sin_family
= AF_INET
;
376 /* copy destination IP address into a string */
377 strcpy(cDestIP
, inet_ntoa(dest
.sin_addr
));
384 * Create our socket which will be used for sending and recieving,
385 * Socket Type is raw, Protocol is ICMP. Also set the TTL value which will be
386 * set in the outgoing IP packet.
393 /* create raw socket */
394 icmpSock
= WSASocket(AF_INET
, SOCK_RAW
, IPPROTO_ICMP
, 0, 0, 0);
395 if (icmpSock
== INVALID_SOCKET
)
397 _tprintf(_T("Could not create socket : %d.\n"), WSAGetLastError());
398 if (WSAGetLastError() == WSAEACCES
)
400 _tprintf(_T("\n\nYou must be an administrator to run this program!\n\n"));
408 iSockRet
= setsockopt(icmpSock
, IPPROTO_IP
, IP_TTL
, (const char *)&iTTL
, sizeof(iTTL
));
409 if (iSockRet
== SOCKET_ERROR
)
411 _tprintf(_T("TTL setsockopt failed : %d. \n"), WSAGetLastError());
421 * Prepare the ICMP echo request packet for sending.
422 * Calculate the packet checksum
425 VOID
PreparePacket(INT iPacketSize
, INT iSeqNum
)
427 /* assemble ICMP echo request packet */
428 sendpacket
.icmpheader
.type
= ECHO_REQUEST
;
429 sendpacket
.icmpheader
.code
= 0;
430 sendpacket
.icmpheader
.checksum
= 0;
431 sendpacket
.icmpheader
.id
= (USHORT
)GetCurrentProcessId();
432 sendpacket
.icmpheader
.seq
= iSeqNum
;
434 /* calculate checksum of packet */
435 sendpacket
.icmpheader
.checksum
= CheckSum((PUSHORT
)&sendpacket
, sizeof(ICMP_HEADER
) + iPacketSize
);
442 * Get the system time and send the ICMP packet to the destination
446 INT
SendPacket(INT datasize
)
451 iPacketSize
= sizeof(ECHO_REPLY_HEADER
) + datasize
;
454 _tprintf(_T("\nsending packet of %d bytes\n"), iPacketSize
);
457 /* get time packet was sent */
458 lTimeStart
= GetTime();
460 iSockRet
= sendto(icmpSock
, //socket
461 (char *)&sendpacket
, //buffer
462 iPacketSize
, //size of buffer
464 (SOCKADDR
*)&dest
, //destination
465 sizeof(dest
)); //address length
467 if (iSockRet
== SOCKET_ERROR
)
469 if (WSAGetLastError() == WSAEACCES
)
471 _tprintf(_T("\n\nYou must be an administrator to run this program!\n\n"));
478 _tprintf(_T("sendto failed %d\n"), WSAGetLastError());
484 _tprintf(_T("sent %d bytes\n"), iSockRet
);
487 /* return number of bytes sent */
495 * Set up a timeout value and put the socket in a select poll.
496 * Wait until we recieve an IPv4 reply packet in reply to the ICMP
497 * echo request packet and get the time the packet was recieved.
498 * If we don't recieve a packet, do some checking to establish why.
501 INT
ReceivePacket(INT datasize
)
505 int iSockRet
= 0, iSelRet
;
509 /* allow for a larger recv buffer to store ICMP TTL
510 * exceed, IP header and orginal ICMP request */
511 iPacketSize
= MAX_REC_SIZE
+ datasize
;
513 iFromLen
= sizeof(source
);
516 _tprintf(_T("receiving packet. Available buffer, %d bytes\n"), iPacketSize
);
519 /* monitor icmpSock for incomming connections */
521 FD_SET(icmpSock
, &readFDS
);
523 /* set timeout values */
524 timeVal
.tv_sec
= iTimeOut
/ 1000;
525 timeVal
.tv_usec
= iTimeOut
% 1000;
527 iSelRet
= select(0, &readFDS
, NULL
, NULL
, &timeVal
);
529 if ((iSelRet
!= SOCKET_ERROR
) && (iSelRet
!= 0))
531 iSockRet
= recvfrom(icmpSock
, //socket
532 (char *)&recvpacket
, //buffer
533 iPacketSize
, //size of buffer
535 (SOCKADDR
*)&source
, //source address
536 &iFromLen
); //pointer to address length
537 /* get time packet was recieved */
538 lTimeEnd
= GetTime();
539 /* if socket timed out */
541 else if (iSelRet
== 0)
546 else if (iSelRet
== SOCKET_ERROR
)
548 _tprintf(_T("select() failed in sendPacket() %d\n"), WSAGetLastError());
553 if (iSockRet
== SOCKET_ERROR
)
555 _tprintf(_T("recvfrom failed: %d\n"), WSAGetLastError());
560 _tprintf(_T("reveived %d bytes\n"), iSockRet
);
570 * Cast the IPv4 packet to an echo reply and to a TTL exceed.
571 * Check the 'type' field to establish what was recieved, and
572 * ensure the packet is related to the originating process.
573 * It all is well, print the time taken for the round trip.
576 INT
DecodeResponse(INT iPacketSize
, INT iSeqNum
)
578 unsigned short header_len
= recvpacket
.h_len
* 4;
579 /* cast the recieved packet into an ECHO reply and a TTL Exceed so we can check the ID*/
580 ECHO_REPLY_HEADER
*IcmpHdr
= (ECHO_REPLY_HEADER
*)((char*)&recvpacket
+ header_len
);
581 TTL_EXCEED_HEADER
*TTLExceedHdr
= (TTL_EXCEED_HEADER
*)((char *)&recvpacket
+ header_len
);
583 /* Make sure the reply is ok */
584 if (iPacketSize
< header_len
+ ICMP_MIN_SIZE
)
586 _tprintf(_T("too few bytes from %s\n"), inet_ntoa(dest
.sin_addr
));
590 switch (IcmpHdr
->icmpheader
.type
)
593 if (TTLExceedHdr
->OrigIcmpHeader
.id
!= (USHORT
)GetCurrentProcessId())
596 /* we've picked up a packet not related to this process
597 * probably from another local program. We ignore it */
599 _tprintf(_T("header id, process id %d"), TTLExceedHdr
->OrigIcmpHeader
.id
, GetCurrentProcessId());
601 //_tprintf(_T("oops ");
604 _tprintf(_T("%3Ld ms"), (lTimeEnd
- lTimeStart
) / TicksPerMs
.QuadPart
);
607 if (IcmpHdr
->icmpheader
.id
!= (USHORT
)GetCurrentProcessId())
610 /* we've picked up a packet not related to this process
611 * probably from another local program. We ignore it */
613 _tprintf(_T("\nPicked up wrong packet. icmpheader.id = %d and process id = %d"), IcmpHdr
->icmpheader
.id
, GetCurrentProcessId());
615 //_tprintf(_T("oops ");
618 _tprintf(_T("%3Ld ms"), (lTimeEnd
- lTimeStart
) / TicksPerMs
.QuadPart
);
620 case DEST_UNREACHABLE
:
624 /* unknown ICMP packet */
632 * Get the system time using preformance counters if available,
633 * otherwise fall back to GetTickCount()
641 if (bUsePerformanceCounter
)
643 if (QueryPerformanceCounter(&Time
) == 0)
645 Time
.u
.LowPart
= (DWORD
)GetTickCount();
647 return (LONGLONG
)Time
.u
.LowPart
;
652 Time
.u
.LowPart
= (DWORD
)GetTickCount();
654 return (LONGLONG
)Time
.u
.LowPart
;
656 return Time
.QuadPart
;
662 * Calculate packet checksum.
665 WORD
CheckSum(PUSHORT data
, UINT size
)
672 size
-= sizeof(USHORT
);
676 dwSum
+= *(UCHAR
*)data
;
678 dwSum
= (dwSum
>> 16) + (dwSum
& 0xFFFF);
679 dwSum
+= (dwSum
>> 16);
681 return (USHORT
)(~dwSum
);
687 * print program usage to screen
692 _tprintf(_T("\nUsage: tracert [-d] [-h maximum_hops] [-j host-list] [-w timeout] target_name\n\n"
694 " -d Do not resolve addresses to hostnames.\n"
695 " -h maximum_hops Maximum number of hops to search for target.\n"
696 " -j host-list Loose source route along host-list.\n"
697 " -w timeout Wait timeout milliseconds for each reply.\n\n"));
699 /* temp notes to stop user questions until getnameinfo/gethostbyaddr and getsockopt are implemented */
700 _tprintf(_T("NOTES\n-----\n"
701 "- Setting TTL values is not currently supported in ReactOS, so the trace will\n"
702 " jump straight to the destination. This feature will be implemented soon.\n"
703 "- Host info is not currently available in ReactOS and will fail with strange\n"
704 " results. Use -d to force it not to resolve IP's.\n"
705 "- For testing purposes, all should work as normal in a Windows environment\n\n"));
712 * Program entry point
715 int main(int argc
, char* argv
[])
717 if (!ParseCmdline(argc
, _argv
)) return -1;