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