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