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