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