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 #ifdef _WIN64
300         PICMP_ECHO_REPLY32 EchoReplyV4;
301         EchoReplyV4 = (PICMP_ECHO_REPLY32)ReplyBuffer;
302 #else
303         PICMP_ECHO_REPLY EchoReplyV4;
304         EchoReplyV4 = (PICMP_ECHO_REPLY)ReplyBuffer;
305 #endif
306         Status = EchoReplyV4->Status;
307         RoundTripTime = EchoReplyV4->RoundTripTime;
308         AddressInfo = &EchoReplyV4->Address;
309     }
310 
311     switch (Status)
312     {
313     case IP_SUCCESS:
314     case IP_TTL_EXPIRED_TRANSIT:
315         if (RoundTripTime)
316         {
317             OutputText(IDS_HOP_TIME, RoundTripTime);
318         }
319         else
320         {
321             OutputText(IDS_HOP_ZERO);
322         }
323         break;
324 
325     case IP_DEST_HOST_UNREACHABLE:
326     case IP_DEST_NET_UNREACHABLE:
327         FoundTarget = true;
328         PrintHopInfo(AddressInfo);
329         OutputText(IDS_HOP_RESPONSE);
330         if (Status == IP_DEST_HOST_UNREACHABLE)
331         {
332             OutputText(IDS_DEST_HOST_UNREACHABLE);
333         }
334         else if (Status == IP_DEST_NET_UNREACHABLE)
335         {
336             OutputText(IDS_DEST_NET_UNREACHABLE);
337         }
338         return true;
339 
340     case IP_REQ_TIMED_OUT:
341         OutputText(IDS_TIMEOUT);
342         break;
343 
344     case IP_GENERAL_FAILURE:
345         OutputText(IDS_GEN_FAILURE);
346         return false;
347 
348     default:
349         OutputText(IDS_TRANSMIT_FAILED, Status);
350         return false;
351     }
352 
353     if (OutputHopAddress)
354     {
355         if (Status == IP_SUCCESS)
356         {
357             FoundTarget = true;
358         }
359         if (Status == IP_TTL_EXPIRED_TRANSIT || Status == IP_SUCCESS)
360         {
361             PrintHopInfo(AddressInfo);
362             OutputText(IDS_LINEBREAK);
363         }
364         else if (Status == IP_REQ_TIMED_OUT)
365         {
366             OutputText(IDS_REQ_TIMED_OUT);
367         }
368     }
369 
370     return true;
371 }
372 
373 static bool
374 RunTraceRoute()
375 {
376     bool Success = false;
377     Success = ResolveTarget();
378     if (!Success)
379     {
380         OutputText(IDS_UNABLE_RESOLVE, Info.HostName);
381         return false;
382     }
383 
384     BYTE SendBuffer[PACKET_SIZE];
385 
386     PVOID ReplyBuffer;
387 
388     DWORD ReplySize = PACKET_SIZE + SIZEOF_ICMP_ERROR + SIZEOF_IO_STATUS_BLOCK;
389     if (Info.Family == AF_INET6)
390     {
391         ReplySize += sizeof(ICMPV6_ECHO_REPLY);
392     }
393     else
394     {
395 #ifdef _WIN64
396         ReplySize += sizeof(ICMP_ECHO_REPLY32);
397 #else
398         ReplySize += sizeof(ICMP_ECHO_REPLY);
399 #endif
400     }
401 
402     HANDLE heap = GetProcessHeap();
403     ReplyBuffer = HeapAlloc(heap, HEAP_ZERO_MEMORY, ReplySize);
404     if (ReplyBuffer == NULL)
405     {
406         FreeAddrInfoW(Info.Target);
407         return false;
408     }
409 
410     if (Info.Family == AF_INET6)
411     {
412         Info.hIcmpFile = Icmp6CreateFile();
413     }
414     else
415     {
416         Info.hIcmpFile = IcmpCreateFile();
417     }
418     if (Info.hIcmpFile == INVALID_HANDLE_VALUE)
419     {
420         HeapFree(heap, 0, ReplyBuffer);
421         FreeAddrInfoW(Info.Target);
422         return false;
423     }
424 
425     OutputText(IDS_TRACE_INFO, Info.HostName, Info.TargetIP, Info.MaxHops);
426 
427     IP_OPTION_INFORMATION IpOptionInfo;
428     ZeroMemory(&IpOptionInfo, sizeof(IpOptionInfo));
429 
430     bool Quit = false;
431     ULONG HopCount = 1;
432     bool FoundTarget = false;
433     while ((HopCount <= Info.MaxHops) && (FoundTarget == false) && (Quit == false))
434     {
435         OutputText(IDS_HOP_COUNT, HopCount);
436 
437         for (int Ping = 1; Ping <= NUM_OF_PINGS; Ping++)
438         {
439             IpOptionInfo.Ttl = static_cast<UCHAR>(HopCount);
440 
441             if (Info.Family == AF_INET6)
442             {
443                 struct sockaddr_in6 Source;
444 
445                 ZeroMemory(&Source, sizeof(Source));
446                 Source.sin6_family = AF_INET6;
447 
448                 (void)Icmp6SendEcho2(Info.hIcmpFile,
449                                      NULL,
450                                      NULL,
451                                      NULL,
452                                      &Source,
453                                      (struct sockaddr_in6 *)Info.Target->ai_addr,
454                                      SendBuffer,
455                                      (USHORT)PACKET_SIZE,
456                                      &IpOptionInfo,
457                                      ReplyBuffer,
458                                      ReplySize,
459                                      Info.Timeout);
460             }
461             else
462             {
463                 (void)IcmpSendEcho2(Info.hIcmpFile,
464                                      NULL,
465                                      NULL,
466                                      NULL,
467                                      ((PSOCKADDR_IN)Info.Target->ai_addr)->sin_addr.s_addr,
468                                      SendBuffer,
469                                      (USHORT)PACKET_SIZE,
470                                      &IpOptionInfo,
471                                      ReplyBuffer,
472                                      ReplySize,
473                                      Info.Timeout);
474             }
475 
476             if (DecodeResponse(ReplyBuffer, (Ping == NUM_OF_PINGS), FoundTarget) == false)
477             {
478                 Quit = true;
479                 break;
480             }
481 
482             if (FoundTarget)
483             {
484                 Success = true;
485                 break;
486             }
487         }
488 
489         HopCount++;
490         Sleep(100);
491     }
492 
493     OutputText(IDS_TRACE_COMPLETE);
494 
495     HeapFree(heap, 0, ReplyBuffer);
496     FreeAddrInfoW(Info.Target);
497     if (Info.hIcmpFile)
498     {
499         IcmpCloseHandle(Info.hIcmpFile);
500     }
501 
502     return Success;
503 }
504 
505 static bool
506 ParseCmdline(int argc, wchar_t *argv[])
507 {
508     if (argc < 2)
509     {
510         Usage();
511         return false;
512     }
513 
514     for (int i = 1; i < argc; i++)
515     {
516         if (argv[i][0] == '-')
517         {
518             switch (argv[i][1])
519             {
520             case 'd':
521                 Info.ResolveAddresses = FALSE;
522                 break;
523 
524             case 'h':
525                 Info.MaxHops = GetULONG(argv[++i]);
526                 break;
527 
528             case 'j':
529                 printf("-j is not yet implemented.\n");
530                 return false;
531 
532             case 'w':
533                 Info.Timeout = GetULONG(argv[++i]);
534                 break;
535 
536             case '4':
537                 Info.Family = AF_INET;
538                 break;
539 
540             case '6':
541                 Info.Family = AF_INET6;
542                 break;
543 
544             default:
545             {
546                 OutputText(IDS_INVALID_OPTION, argv[i]);
547                 Usage();
548                 return false;
549             }
550             }
551         }
552         else
553         {
554             StringCchCopyW(Info.HostName, NI_MAXHOST, argv[i]);
555             break;
556         }
557     }
558 
559     return true;
560 }
561 
562 EXTERN_C
563 int wmain(int argc, wchar_t *argv[])
564 {
565 #ifdef USE_CONUTILS
566     /* Initialize the Console Standard Streams */
567     ConInitStdStreams();
568 #endif
569 
570     Info.ResolveAddresses = true;
571     Info.MaxHops = 30;
572     Info.Timeout = 4000;
573     Info.Family = AF_UNSPEC;
574 
575     if (!ParseCmdline(argc, argv))
576     {
577         return 1;
578     }
579 
580     WSADATA WsaData;
581     if (WSAStartup(MAKEWORD(2, 2), &WsaData))
582     {
583         return 1;
584     }
585 
586     bool Success;
587     Success = RunTraceRoute();
588 
589     WSACleanup();
590 
591     return Success ? 0 : 1;
592 }
593