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