1cabd0a2dd07c7ecd8152e5a7878688e94982bd3
[reactos.git] / base / applications / network / tracert / tracert.cpp
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
381 PVOID ReplyBuffer;
382
383 DWORD ReplySize = PACKET_SIZE + SIZEOF_ICMP_ERROR + SIZEOF_IO_STATUS_BLOCK;
384 if (Info.Family == AF_INET6)
385 {
386 ReplySize += sizeof(ICMPV6_ECHO_REPLY);
387 }
388 else
389 {
390 #ifdef _WIN64
391 ReplySize += sizeof(ICMP_ECHO_REPLY32);
392 #else
393 ReplySize += sizeof(ICMP_ECHO_REPLY);
394 #endif
395 }
396
397 HANDLE heap = GetProcessHeap();
398 ReplyBuffer = HeapAlloc(heap, HEAP_ZERO_MEMORY, ReplySize);
399 if (ReplyBuffer == NULL)
400 {
401 FreeAddrInfoW(Info.Target);
402 return false;
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 HeapFree(heap, 0, ReplyBuffer);
416 FreeAddrInfoW(Info.Target);
417 return false;
418 }
419
420 OutputText(IDS_TRACE_INFO, Info.HostName, Info.TargetIP, Info.MaxHops);
421
422 IP_OPTION_INFORMATION IpOptionInfo;
423 ZeroMemory(&IpOptionInfo, sizeof(IpOptionInfo));
424
425 bool Quit = false;
426 ULONG HopCount = 1;
427 bool FoundTarget = false;
428 while ((HopCount <= Info.MaxHops) && (FoundTarget == false) && (Quit == false))
429 {
430 OutputText(IDS_HOP_COUNT, HopCount);
431
432 for (int Ping = 1; Ping <= NUM_OF_PINGS; Ping++)
433 {
434 IpOptionInfo.Ttl = static_cast<UCHAR>(HopCount);
435
436 if (Info.Family == AF_INET6)
437 {
438 struct sockaddr_in6 Source;
439
440 ZeroMemory(&Source, sizeof(Source));
441 Source.sin6_family = AF_INET6;
442
443 (void)Icmp6SendEcho2(Info.hIcmpFile,
444 NULL,
445 NULL,
446 NULL,
447 &Source,
448 (struct sockaddr_in6 *)Info.Target->ai_addr,
449 SendBuffer,
450 (USHORT)PACKET_SIZE,
451 &IpOptionInfo,
452 ReplyBuffer,
453 ReplySize,
454 Info.Timeout);
455 }
456 else
457 {
458 (void)IcmpSendEcho2(Info.hIcmpFile,
459 NULL,
460 NULL,
461 NULL,
462 ((PSOCKADDR_IN)Info.Target->ai_addr)->sin_addr.s_addr,
463 SendBuffer,
464 (USHORT)PACKET_SIZE,
465 &IpOptionInfo,
466 ReplyBuffer,
467 ReplySize,
468 Info.Timeout);
469 }
470
471 if (DecodeResponse(ReplyBuffer, (Ping == NUM_OF_PINGS), FoundTarget) == false)
472 {
473 Quit = true;
474 break;
475 }
476
477 if (FoundTarget)
478 {
479 Success = true;
480 break;
481 }
482 }
483
484 HopCount++;
485 Sleep(100);
486 }
487
488 OutputText(IDS_TRACE_COMPLETE);
489
490 HeapFree(heap, 0, ReplyBuffer);
491 FreeAddrInfoW(Info.Target);
492 if (Info.hIcmpFile)
493 {
494 IcmpCloseHandle(Info.hIcmpFile);
495 }
496
497 return Success;
498 }
499
500 static bool
501 ParseCmdline(int argc, wchar_t *argv[])
502 {
503 if (argc < 2)
504 {
505 Usage();
506 return false;
507 }
508
509 for (int i = 1; i < argc; i++)
510 {
511 if (argv[i][0] == '-')
512 {
513 switch (argv[i][1])
514 {
515 case 'd':
516 Info.ResolveAddresses = FALSE;
517 break;
518
519 case 'h':
520 Info.MaxHops = GetULONG(argv[++i]);
521 break;
522
523 case 'j':
524 printf("-j is not yet implemented.\n");
525 return false;
526
527 case 'w':
528 Info.Timeout = GetULONG(argv[++i]);
529 break;
530
531 case '4':
532 Info.Family = AF_INET;
533 break;
534
535 case '6':
536 Info.Family = AF_INET6;
537 break;
538
539 default:
540 {
541 OutputText(IDS_INVALID_OPTION, argv[i]);
542 Usage();
543 return false;
544 }
545 }
546 }
547 else
548 {
549 StringCchCopyW(Info.HostName, NI_MAXHOST, argv[i]);
550 break;
551 }
552 }
553
554 return true;
555 }
556
557 EXTERN_C
558 int wmain(int argc, wchar_t *argv[])
559 {
560 #ifdef USE_CONUTILS
561 /* Initialize the Console Standard Streams */
562 ConInitStdStreams();
563 #endif
564
565 Info.ResolveAddresses = true;
566 Info.MaxHops = 30;
567 Info.Timeout = 4000;
568 Info.Family = AF_UNSPEC;
569
570 if (!ParseCmdline(argc, argv))
571 {
572 return 1;
573 }
574
575 WSADATA WsaData;
576 if (WSAStartup(MAKEWORD(2, 2), &WsaData))
577 {
578 return 1;
579 }
580
581 bool Success;
582 Success = RunTraceRoute();
583
584 WSACleanup();
585
586 return Success ? 0 : 1;
587 }