1 /*
2  * PROJECT:     ReactOS trace route utility
3  * LICENSE:     GPL - See COPYING in the top level directory
4  * FILE:        base/applications/network/tracert/tracert.cpp
5  * PURPOSE:     Trace network paths through networks
6  * COPYRIGHT:   Copyright 2018 Ged Murphy <gedmurphy@reactos.org>
7  *
8  */
9 
10 #ifdef __REACTOS__
11 #define USE_CONUTILS
12 #define WIN32_NO_STATUS
13 #include <stdarg.h>
14 #include <windef.h>
15 #include <winbase.h>
16 #include <winuser.h>
17 #define _INC_WINDOWS
18 #include <stdlib.h>
19 #include <winsock2.h>
20 #include <conutils.h>
21 #else
22 #include <winsock2.h>
23 #include <Windows.h>
24 #endif
25 #include <ws2tcpip.h>
26 #include <iphlpapi.h>
27 #include <icmpapi.h>
28 #include <strsafe.h>
29 #include "resource.h"
30 
31 #define SIZEOF_ICMP_ERROR       8
32 #define SIZEOF_IO_STATUS_BLOCK  8
33 #define PACKET_SIZE             32
34 #define MAX_IPADDRESS           32
35 #define NUM_OF_PINGS            3
36 
37 struct TraceInfo
38 {
39     bool ResolveAddresses;
40     ULONG MaxHops;
41     ULONG Timeout;
42     WCHAR HostName[NI_MAXHOST];
43     WCHAR TargetIP[MAX_IPADDRESS];
44     int Family;
45 
46     HANDLE hIcmpFile;
47     PADDRINFOW Target;
48 
49 } Info = { 0 };
50 
51 
52 
53 #ifndef USE_CONUTILS
54 static
55 INT
56 LengthOfStrResource(
57     _In_ HINSTANCE hInst,
58     _In_ UINT uID
59 )
60 {
61     HRSRC hrSrc;
62     HGLOBAL hRes;
63     LPWSTR lpName, lpStr;
64 
65     if (hInst == NULL) return -1;
66 
67     lpName = (LPWSTR)MAKEINTRESOURCE((uID >> 4) + 1);
68 
69     if ((hrSrc = FindResourceW(hInst, lpName, (LPWSTR)RT_STRING)) &&
70         (hRes = LoadResource(hInst, hrSrc)) &&
71         (lpStr = (WCHAR*)LockResource(hRes)))
72     {
73         UINT x;
74         uID &= 0xF;
75         for (x = 0; x < uID; x++)
76         {
77             lpStr += (*lpStr) + 1;
78         }
79         return (int)(*lpStr);
80     }
81     return -1;
82 }
83 
84 static
85 INT
86 AllocAndLoadString(
87     _In_ UINT uID,
88     _Out_ LPWSTR *lpTarget
89 )
90 {
91     HMODULE hInst;
92     INT Length;
93 
94     hInst = GetModuleHandleW(NULL);
95     Length = LengthOfStrResource(hInst, uID);
96     if (Length++ > 0)
97     {
98         (*lpTarget) = (LPWSTR)LocalAlloc(LMEM_FIXED,
99                                          Length * sizeof(WCHAR));
100         if ((*lpTarget) != NULL)
101         {
102             INT Ret;
103             if (!(Ret = LoadStringW(hInst, uID, *lpTarget, Length)))
104             {
105                 LocalFree((HLOCAL)(*lpTarget));
106             }
107             return Ret;
108         }
109     }
110     return 0;
111 }
112 
113 static
114 INT
115 OutputText(
116     _In_ UINT uID,
117     ...)
118 {
119     LPWSTR Format;
120     DWORD Ret = 0;
121     va_list lArgs;
122 
123     if (AllocAndLoadString(uID, &Format) > 0)
124     {
125         va_start(lArgs, uID);
126 
127         LPWSTR Buffer;
128         Ret = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING,
129                              Format,
130                              0,
131                              0,
132                              (LPWSTR)&Buffer,
133                              0,
134                              &lArgs);
135         va_end(lArgs);
136 
137         if (Ret)
138         {
139             wprintf(Buffer);
140             LocalFree(Buffer);
141         }
142         LocalFree((HLOCAL)Format);
143     }
144 
145     return Ret;
146 }
147 #else
148 #define OutputText(Id, ...) ConResMsgPrintfEx(StdOut, NULL, 0, Id, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), ##__VA_ARGS__)
149 #endif //USE_CONUTILS
150 
151 static
152 VOID
153 Usage()
154 {
155     OutputText(IDS_USAGE);
156 }
157 
158 static ULONG
159 GetULONG(
160     _In_z_ LPWSTR String
161 )
162 {
163     ULONG Length;
164     Length = wcslen(String);
165 
166     ULONG i = 0;
167     while ((i < Length) && ((String[i] < L'0') || (String[i] > L'9'))) i++;
168     if ((i >= Length) || ((String[i] < L'0') || (String[i] > L'9')))
169     {
170         return (ULONG)-1;
171     }
172 
173     LPWSTR StopString;
174     return wcstoul(&String[i], &StopString, 10);
175 }
176 
177 static bool
178 ResolveTarget()
179 {
180     ADDRINFOW Hints;
181     ZeroMemory(&Hints, sizeof(Hints));
182     Hints.ai_family = Info.Family;
183     Hints.ai_flags = AI_CANONNAME;
184 
185     int Status;
186     Status = GetAddrInfoW(Info.HostName,
187                           NULL,
188                           &Hints,
189                           &Info.Target);
190     if (Status != 0)
191     {
192         return false;
193     }
194 
195     Status = GetNameInfoW(Info.Target->ai_addr,
196                           Info.Target->ai_addrlen,
197                           Info.TargetIP,
198                           MAX_IPADDRESS,
199                           NULL,
200                           0,
201                           NI_NUMERICHOST);
202     if (Status != 0)
203     {
204         return false;
205     }
206 
207     return true;
208 }
209 
210 static bool
211 PrintHopInfo(_In_ PVOID Buffer)
212 {
213     SOCKADDR_IN6 SockAddrIn6 = { 0 };
214     SOCKADDR_IN SockAddrIn = { 0 };
215     PSOCKADDR SockAddr;
216     socklen_t Size;
217 
218     if (Info.Family == AF_INET6)
219     {
220         PIPV6_ADDRESS_EX Ipv6Addr = (PIPV6_ADDRESS_EX)Buffer;
221         SockAddrIn6.sin6_family = AF_INET6;
222         CopyMemory(SockAddrIn6.sin6_addr.u.Word, Ipv6Addr->sin6_addr, sizeof(SockAddrIn6.sin6_addr));
223         //SockAddrIn6.sin6_addr = Ipv6Addr->sin6_addr;
224         SockAddr = (PSOCKADDR)&SockAddrIn6;
225         Size = sizeof(SOCKADDR_IN6);
226 
227     }
228     else
229     {
230         IPAddr *Address = (IPAddr *)Buffer;
231         SockAddrIn.sin_family = AF_INET;
232         SockAddrIn.sin_addr.S_un.S_addr = *Address;
233         SockAddr = (PSOCKADDR)&SockAddrIn;
234         Size = sizeof(SOCKADDR_IN);
235     }
236 
237     INT Status;
238     bool Resolved = false;
239     WCHAR HostName[NI_MAXHOST];
240     if (Info.ResolveAddresses)
241     {
242         Status = GetNameInfoW(SockAddr,
243                               Size,
244                               HostName,
245                               NI_MAXHOST,
246                               NULL,
247                               0,
248                               NI_NAMEREQD);
249         if (Status == 0)
250         {
251             Resolved = true;
252         }
253     }
254 
255     WCHAR IpAddress[MAX_IPADDRESS];
256     Status = GetNameInfoW(SockAddr,
257                           Size,
258                           IpAddress,
259                           MAX_IPADDRESS,
260                           NULL,
261                           0,
262                           NI_NUMERICHOST);
263     if (Status == 0)
264     {
265         if (Resolved)
266         {
267             OutputText(IDS_HOP_RES_INFO, HostName, IpAddress);
268         }
269         else
270         {
271             OutputText(IDS_HOP_IP_INFO, IpAddress);
272         }
273     }
274 
275     return (Status == 0);
276 }
277 
278 static bool
279 DecodeResponse(
280     _In_ PVOID ReplyBuffer,
281     _In_ bool OutputHopAddress,
282     _Out_ bool& FoundTarget
283 )
284 {
285     ULONG RoundTripTime;
286     PVOID AddressInfo;
287     ULONG Status;
288 
289     if (Info.Family == AF_INET6)
290     {
291         PICMPV6_ECHO_REPLY EchoReplyV6;
292         EchoReplyV6 = (PICMPV6_ECHO_REPLY)ReplyBuffer;
293         Status = EchoReplyV6->Status;
294         RoundTripTime = EchoReplyV6->RoundTripTime;
295         AddressInfo = &EchoReplyV6->Address;
296     }
297     else
298     {
299         PICMP_ECHO_REPLY EchoReplyV4;
300         EchoReplyV4 = (PICMP_ECHO_REPLY)ReplyBuffer;
301         Status = EchoReplyV4->Status;
302         RoundTripTime = EchoReplyV4->RoundTripTime;
303         AddressInfo = &EchoReplyV4->Address;
304     }
305 
306     switch (Status)
307     {
308     case IP_SUCCESS:
309     case IP_TTL_EXPIRED_TRANSIT:
310         if (RoundTripTime)
311         {
312             OutputText(IDS_HOP_TIME, RoundTripTime);
313         }
314         else
315         {
316             OutputText(IDS_HOP_ZERO);
317         }
318         break;
319 
320     case IP_DEST_HOST_UNREACHABLE:
321     case IP_DEST_NET_UNREACHABLE:
322         FoundTarget = true;
323         PrintHopInfo(AddressInfo);
324         OutputText(IDS_HOP_RESPONSE);
325         if (Status == IP_DEST_HOST_UNREACHABLE)
326         {
327             OutputText(IDS_DEST_HOST_UNREACHABLE);
328         }
329         else if (Status == IP_DEST_NET_UNREACHABLE)
330         {
331             OutputText(IDS_DEST_NET_UNREACHABLE);
332         }
333         return true;
334 
335     case IP_REQ_TIMED_OUT:
336         OutputText(IDS_TIMEOUT);
337         break;
338 
339     case IP_GENERAL_FAILURE:
340         OutputText(IDS_GEN_FAILURE);
341         return false;
342 
343     default:
344         OutputText(IDS_TRANSMIT_FAILED, Status);
345         return false;
346     }
347 
348     if (OutputHopAddress)
349     {
350         if (Status == IP_SUCCESS)
351         {
352             FoundTarget = true;
353         }
354         if (Status == IP_TTL_EXPIRED_TRANSIT || Status == IP_SUCCESS)
355         {
356             PrintHopInfo(AddressInfo);
357             OutputText(IDS_LINEBREAK);
358         }
359         else if (Status == IP_REQ_TIMED_OUT)
360         {
361             OutputText(IDS_REQ_TIMED_OUT);
362         }
363     }
364 
365     return true;
366 }
367 
368 static bool
369 RunTraceRoute()
370 {
371     bool Success = false;
372     Success = ResolveTarget();
373     if (!Success)
374     {
375         OutputText(IDS_UNABLE_RESOLVE, Info.HostName);
376         return false;
377     }
378 
379     BYTE SendBuffer[PACKET_SIZE];
380     ICMPV6_ECHO_REPLY ReplyBufferv6;
381 #ifdef _WIN64
382     ICMP_ECHO_REPLY32 ReplyBufferv432;
383 #else
384     ICMP_ECHO_REPLY ReplyBufferv4;
385 #endif
386     PVOID ReplyBuffer;
387 
388     DWORD ReplySize = PACKET_SIZE + SIZEOF_ICMP_ERROR + SIZEOF_IO_STATUS_BLOCK;
389     if (Info.Family == AF_INET6)
390     {
391         ReplyBuffer = &ReplyBufferv6;
392         ReplySize += sizeof(ICMPV6_ECHO_REPLY);
393     }
394     else
395     {
396 #ifdef _WIN64
397         ReplyBuffer = &ReplyBufferv432;
398         ReplySize += sizeof(ICMP_ECHO_REPLY32);
399 #else
400         ReplyBuffer = &ReplyBufferv4;
401         ReplySize += sizeof(ICMP_ECHO_REPLY);
402 #endif
403     }
404 
405     if (Info.Family == AF_INET6)
406     {
407         Info.hIcmpFile = Icmp6CreateFile();
408     }
409     else
410     {
411         Info.hIcmpFile = IcmpCreateFile();
412     }
413     if (Info.hIcmpFile == INVALID_HANDLE_VALUE)
414     {
415         FreeAddrInfoW(Info.Target);
416         return false;
417     }
418 
419     OutputText(IDS_TRACE_INFO, Info.HostName, Info.TargetIP, Info.MaxHops);
420 
421     IP_OPTION_INFORMATION IpOptionInfo;
422     ZeroMemory(&IpOptionInfo, sizeof(IpOptionInfo));
423 
424     bool Quit = false;
425     ULONG HopCount = 1;
426     bool FoundTarget = false;
427     while ((HopCount <= Info.MaxHops) && (FoundTarget == false) && (Quit == false))
428     {
429         OutputText(IDS_HOP_COUNT, HopCount);
430 
431         for (int Ping = 1; Ping <= NUM_OF_PINGS; Ping++)
432         {
433             IpOptionInfo.Ttl = static_cast<UCHAR>(HopCount);
434 
435             if (Info.Family == AF_INET6)
436             {
437                 struct sockaddr_in6 Source;
438 
439                 ZeroMemory(&Source, sizeof(Source));
440                 Source.sin6_family = AF_INET6;
441 
442                 (void)Icmp6SendEcho2(Info.hIcmpFile,
443                                      NULL,
444                                      NULL,
445                                      NULL,
446                                      &Source,
447                                      (struct sockaddr_in6 *)Info.Target->ai_addr,
448                                      SendBuffer,
449                                      (USHORT)PACKET_SIZE,
450                                      &IpOptionInfo,
451                                      ReplyBuffer,
452                                      ReplySize,
453                                      Info.Timeout);
454             }
455             else
456             {
457                 (void)IcmpSendEcho2(Info.hIcmpFile,
458                                      NULL,
459                                      NULL,
460                                      NULL,
461                                      ((PSOCKADDR_IN)Info.Target->ai_addr)->sin_addr.s_addr,
462                                      SendBuffer,
463                                      (USHORT)PACKET_SIZE,
464                                      &IpOptionInfo,
465                                      ReplyBuffer,
466                                      ReplySize,
467                                      Info.Timeout);
468             }
469 
470             if (DecodeResponse(ReplyBuffer, (Ping == NUM_OF_PINGS), FoundTarget) == false)
471             {
472                 Quit = true;
473                 break;
474             }
475 
476             if (FoundTarget)
477             {
478                 Success = true;
479                 break;
480             }
481         }
482 
483         HopCount++;
484         Sleep(100);
485     }
486 
487     OutputText(IDS_TRACE_COMPLETE);
488 
489     FreeAddrInfoW(Info.Target);
490     if (Info.hIcmpFile)
491     {
492         IcmpCloseHandle(Info.hIcmpFile);
493     }
494 
495     return Success;
496 }
497 
498 static bool
499 ParseCmdline(int argc, wchar_t *argv[])
500 {
501     if (argc < 2)
502     {
503         Usage();
504         return false;
505     }
506 
507     for (int i = 1; i < argc; i++)
508     {
509         if (argv[i][0] == '-')
510         {
511             switch (argv[i][1])
512             {
513             case 'd':
514                 Info.ResolveAddresses = FALSE;
515                 break;
516 
517             case 'h':
518                 Info.MaxHops = GetULONG(argv[++i]);
519                 break;
520 
521             case 'j':
522                 printf("-j is not yet implemented.\n");
523                 return false;
524 
525             case 'w':
526                 Info.Timeout = GetULONG(argv[++i]);
527                 break;
528 
529             case '4':
530                 Info.Family = AF_INET;
531                 break;
532 
533             case '6':
534                 Info.Family = AF_INET6;
535                 break;
536 
537             default:
538             {
539                 OutputText(IDS_INVALID_OPTION, argv[i]);
540                 Usage();
541                 return false;
542             }
543             }
544         }
545         else
546         {
547             StringCchCopyW(Info.HostName, NI_MAXHOST, argv[i]);
548             break;
549         }
550     }
551 
552     return true;
553 }
554 
555 EXTERN_C
556 int wmain(int argc, wchar_t *argv[])
557 {
558 #ifdef USE_CONUTILS
559     /* Initialize the Console Standard Streams */
560     ConInitStdStreams();
561 #endif
562 
563     Info.ResolveAddresses = true;
564     Info.MaxHops = 30;
565     Info.Timeout = 4000;
566     Info.Family = AF_UNSPEC;
567 
568     if (!ParseCmdline(argc, argv))
569     {
570         return 1;
571     }
572 
573     WSADATA WsaData;
574     if (WSAStartup(MAKEWORD(2, 2), &WsaData))
575     {
576         return 1;
577     }
578 
579     bool Success;
580     Success = RunTraceRoute();
581 
582     WSACleanup();
583 
584     return Success ? 0 : 1;
585 }
586