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 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 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 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 151 Usage() 152 { 153 OutputText(IDS_USAGE); 154 } 155 156 static ULONG 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 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 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 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 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 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 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 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