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 WIN32_NO_STATUS 12 #include <stdarg.h> 13 #include <windef.h> 14 #include <winbase.h> 15 #include <winuser.h> 16 #define _INC_WINDOWS 17 #include <stdio.h> 18 #include <stdlib.h> 19 #include <winsock2.h> 20 #else 21 #include <winsock2.h> 22 #include <Windows.h> 23 #endif 24 #include <ws2tcpip.h> 25 #include <iphlpapi.h> 26 #include <icmpapi.h> 27 #include <strsafe.h> 28 #include "resource.h" 29 30 #define SIZEOF_ICMP_ERROR 8 31 #define SIZEOF_IO_STATUS_BLOCK 8 32 #define PACKET_SIZE 32 33 #define MAX_IPADDRESS 32 34 #define NUM_OF_PINGS 3 35 36 struct TraceInfo 37 { 38 bool ResolveAddresses; 39 ULONG MaxHops; 40 ULONG Timeout; 41 WCHAR HostName[NI_MAXHOST]; 42 WCHAR TargetIP[MAX_IPADDRESS]; 43 int Family; 44 45 HANDLE hIcmpFile; 46 PADDRINFOW Target; 47 48 } Info = { 0 }; 49 50 51 52 #ifndef USE_CONUTILS 53 static 54 INT 55 LengthOfStrResource( 56 _In_ HINSTANCE hInst, 57 _In_ UINT uID 58 ) 59 { 60 HRSRC hrSrc; 61 HGLOBAL hRes; 62 LPWSTR lpName, lpStr; 63 64 if (hInst == NULL) return -1; 65 66 lpName = (LPWSTR)MAKEINTRESOURCE((uID >> 4) + 1); 67 68 if ((hrSrc = FindResourceW(hInst, lpName, (LPWSTR)RT_STRING)) && 69 (hRes = LoadResource(hInst, hrSrc)) && 70 (lpStr = (WCHAR*)LockResource(hRes))) 71 { 72 UINT x; 73 uID &= 0xF; 74 for (x = 0; x < uID; x++) 75 { 76 lpStr += (*lpStr) + 1; 77 } 78 return (int)(*lpStr); 79 } 80 return -1; 81 } 82 83 static 84 INT 85 AllocAndLoadString( 86 _In_ UINT uID, 87 _Out_ LPWSTR *lpTarget 88 ) 89 { 90 HMODULE hInst; 91 INT Length; 92 93 hInst = GetModuleHandleW(NULL); 94 Length = LengthOfStrResource(hInst, uID); 95 if (Length++ > 0) 96 { 97 (*lpTarget) = (LPWSTR)LocalAlloc(LMEM_FIXED, 98 Length * sizeof(WCHAR)); 99 if ((*lpTarget) != NULL) 100 { 101 INT Ret; 102 if (!(Ret = LoadStringW(hInst, uID, *lpTarget, Length))) 103 { 104 LocalFree((HLOCAL)(*lpTarget)); 105 } 106 return Ret; 107 } 108 } 109 return 0; 110 } 111 112 static 113 INT 114 OutputText( 115 _In_ UINT uID, 116 ...) 117 { 118 LPWSTR Format; 119 DWORD Ret = 0; 120 va_list lArgs; 121 122 if (AllocAndLoadString(uID, &Format) > 0) 123 { 124 va_start(lArgs, uID); 125 126 LPWSTR Buffer; 127 Ret = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING, 128 Format, 129 0, 130 0, 131 (LPWSTR)&Buffer, 132 0, 133 &lArgs); 134 va_end(lArgs); 135 136 if (Ret) 137 { 138 wprintf(Buffer); 139 LocalFree(Buffer); 140 } 141 LocalFree((HLOCAL)Format); 142 } 143 144 return Ret; 145 } 146 #else 147 #define OutputText(Id, ...) ConResPrintf(StdOut, Id, __VA_ARGS__) 148 #endif //USE_CONUTILS 149 150 static 151 VOID 152 Usage() 153 { 154 OutputText(IDS_USAGE); 155 } 156 157 static ULONG 158 GetULONG( 159 _In_z_ LPWSTR String 160 ) 161 { 162 ULONG Length; 163 Length = wcslen(String); 164 165 ULONG i = 0; 166 while ((i < Length) && ((String[i] < L'0') || (String[i] > L'9'))) i++; 167 if ((i >= Length) || ((String[i] < L'0') || (String[i] > L'9'))) 168 { 169 return (ULONG)-1; 170 } 171 172 LPWSTR StopString; 173 return wcstoul(&String[i], &StopString, 10); 174 } 175 176 static bool 177 ResolveTarget() 178 { 179 ADDRINFOW Hints; 180 ZeroMemory(&Hints, sizeof(Hints)); 181 Hints.ai_family = Info.Family; 182 Hints.ai_flags = AI_CANONNAME; 183 184 int Status; 185 Status = GetAddrInfoW(Info.HostName, 186 NULL, 187 &Hints, 188 &Info.Target); 189 if (Status != 0) 190 { 191 return false; 192 } 193 194 Status = GetNameInfoW(Info.Target->ai_addr, 195 Info.Target->ai_addrlen, 196 Info.TargetIP, 197 MAX_IPADDRESS, 198 NULL, 199 0, 200 NI_NUMERICHOST); 201 if (Status != 0) 202 { 203 return false; 204 } 205 206 return true; 207 } 208 209 static bool 210 PrintHopInfo(_In_ PVOID Buffer) 211 { 212 SOCKADDR_IN6 SockAddrIn6 = { 0 }; 213 SOCKADDR_IN SockAddrIn = { 0 }; 214 PSOCKADDR SockAddr; 215 socklen_t Size; 216 217 if (Info.Family == AF_INET6) 218 { 219 PIPV6_ADDRESS_EX Ipv6Addr = (PIPV6_ADDRESS_EX)Buffer; 220 SockAddrIn6.sin6_family = AF_INET6; 221 CopyMemory(SockAddrIn6.sin6_addr.u.Word, Ipv6Addr->sin6_addr, sizeof(SockAddrIn6.sin6_addr)); 222 //SockAddrIn6.sin6_addr = Ipv6Addr->sin6_addr; 223 SockAddr = (PSOCKADDR)&SockAddrIn6; 224 Size = sizeof(SOCKADDR_IN6); 225 226 } 227 else 228 { 229 IPAddr *Address = (IPAddr *)Buffer; 230 SockAddrIn.sin_family = AF_INET; 231 SockAddrIn.sin_addr.S_un.S_addr = *Address; 232 SockAddr = (PSOCKADDR)&SockAddrIn; 233 Size = sizeof(SOCKADDR_IN); 234 } 235 236 INT Status; 237 bool Resolved = false; 238 WCHAR HostName[NI_MAXHOST]; 239 if (Info.ResolveAddresses) 240 { 241 Status = GetNameInfoW(SockAddr, 242 Size, 243 HostName, 244 NI_MAXHOST, 245 NULL, 246 0, 247 NI_NAMEREQD); 248 if (Status == 0) 249 { 250 Resolved = true; 251 } 252 } 253 254 WCHAR IpAddress[MAX_IPADDRESS]; 255 Status = GetNameInfoW(SockAddr, 256 Size, 257 IpAddress, 258 NI_MAXHOST, 259 NULL, 260 0, 261 NI_NUMERICHOST); 262 if (Status == 0) 263 { 264 if (Resolved) 265 { 266 OutputText(IDS_HOP_RES_INFO, HostName, IpAddress); 267 } 268 else 269 { 270 OutputText(IDS_HOP_IP_INFO, IpAddress); 271 } 272 } 273 274 return (Status == 0); 275 } 276 277 static bool 278 DecodeResponse( 279 _In_ PVOID ReplyBuffer, 280 _In_ bool OutputHopAddress, 281 _Out_ bool& FoundTarget 282 ) 283 { 284 ULONG RoundTripTime; 285 PVOID AddressInfo; 286 ULONG Status; 287 288 if (Info.Family == AF_INET6) 289 { 290 PICMPV6_ECHO_REPLY EchoReplyV6; 291 EchoReplyV6 = (PICMPV6_ECHO_REPLY)ReplyBuffer; 292 Status = EchoReplyV6->Status; 293 RoundTripTime = EchoReplyV6->RoundTripTime; 294 AddressInfo = &EchoReplyV6->Address; 295 } 296 else 297 { 298 PICMP_ECHO_REPLY EchoReplyV4; 299 EchoReplyV4 = (PICMP_ECHO_REPLY)ReplyBuffer; 300 Status = EchoReplyV4->Status; 301 RoundTripTime = EchoReplyV4->RoundTripTime; 302 AddressInfo = &EchoReplyV4->Address; 303 } 304 305 switch (Status) 306 { 307 case IP_SUCCESS: 308 case IP_TTL_EXPIRED_TRANSIT: 309 if (RoundTripTime) 310 { 311 OutputText(IDS_HOP_TIME, RoundTripTime); 312 } 313 else 314 { 315 OutputText(IDS_HOP_ZERO); 316 } 317 break; 318 319 case IP_DEST_HOST_UNREACHABLE: 320 case IP_DEST_NET_UNREACHABLE: 321 FoundTarget = true; 322 PrintHopInfo(AddressInfo); 323 OutputText(IDS_HOP_RESPONSE); 324 if (Status == IP_DEST_HOST_UNREACHABLE) 325 { 326 OutputText(IDS_DEST_HOST_UNREACHABLE); 327 } 328 else if (Status == IP_DEST_NET_UNREACHABLE) 329 { 330 OutputText(IDS_DEST_NET_UNREACHABLE); 331 } 332 return true; 333 334 case IP_REQ_TIMED_OUT: 335 OutputText(IDS_TIMEOUT); 336 break; 337 338 case IP_GENERAL_FAILURE: 339 OutputText(IDS_GEN_FAILURE); 340 return false; 341 342 default: 343 OutputText(IDS_TRANSMIT_FAILED, Status); 344 return false; 345 } 346 347 if (OutputHopAddress) 348 { 349 if (Status == IP_SUCCESS) 350 { 351 FoundTarget = true; 352 } 353 if (Status == IP_TTL_EXPIRED_TRANSIT || Status == IP_SUCCESS) 354 { 355 PrintHopInfo(AddressInfo); 356 OutputText(IDS_LINEBREAK); 357 } 358 else if (Status == IP_REQ_TIMED_OUT) 359 { 360 OutputText(IDS_REQ_TIMED_OUT); 361 } 362 } 363 364 return true; 365 } 366 367 static bool 368 RunTraceRoute() 369 { 370 bool Success = false; 371 Success = ResolveTarget(); 372 if (!Success) 373 { 374 OutputText(IDS_UNABLE_RESOLVE, Info.HostName); 375 return false; 376 } 377 378 BYTE SendBuffer[PACKET_SIZE]; 379 ICMPV6_ECHO_REPLY ReplyBufferv6; 380 #ifdef _WIN64 381 ICMP_ECHO_REPLY32 ReplyBufferv432; 382 #else 383 ICMP_ECHO_REPLY ReplyBufferv4; 384 #endif 385 PVOID ReplyBuffer; 386 387 DWORD ReplySize = PACKET_SIZE + SIZEOF_ICMP_ERROR + SIZEOF_IO_STATUS_BLOCK; 388 if (Info.Family == AF_INET6) 389 { 390 ReplyBuffer = &ReplyBufferv6; 391 ReplySize += sizeof(ICMPV6_ECHO_REPLY); 392 } 393 else 394 { 395 #ifdef _WIN64 396 ReplyBuffer = &ReplyBufferv432; 397 ReplySize += sizeof(ICMP_ECHO_REPLY32); 398 #else 399 ReplyBuffer = &ReplyBufferv4; 400 ReplySize += sizeof(ICMP_ECHO_REPLY); 401 #endif 402 } 403 404 if (Info.Family == AF_INET6) 405 { 406 Info.hIcmpFile = Icmp6CreateFile(); 407 } 408 else 409 { 410 Info.hIcmpFile = IcmpCreateFile(); 411 } 412 if (Info.hIcmpFile == INVALID_HANDLE_VALUE) 413 { 414 FreeAddrInfoW(Info.Target); 415 return false; 416 } 417 418 OutputText(IDS_TRACE_INFO, Info.HostName, Info.TargetIP, Info.MaxHops); 419 420 IP_OPTION_INFORMATION IpOptionInfo; 421 ZeroMemory(&IpOptionInfo, sizeof(IpOptionInfo)); 422 423 bool Quit = false; 424 ULONG HopCount = 1; 425 bool FoundTarget = false; 426 while ((HopCount <= Info.MaxHops) && (FoundTarget == false) && (Quit == false)) 427 { 428 OutputText(IDS_HOP_COUNT, HopCount); 429 430 for (int Ping = 1; Ping <= NUM_OF_PINGS; Ping++) 431 { 432 IpOptionInfo.Ttl = static_cast<UCHAR>(HopCount); 433 434 if (Info.Family == AF_INET6) 435 { 436 struct sockaddr_in6 Source; 437 438 ZeroMemory(&Source, sizeof(Source)); 439 Source.sin6_family = AF_INET6; 440 441 (void)Icmp6SendEcho2(Info.hIcmpFile, 442 NULL, 443 NULL, 444 NULL, 445 &Source, 446 (struct sockaddr_in6 *)Info.Target->ai_addr, 447 SendBuffer, 448 (USHORT)PACKET_SIZE, 449 &IpOptionInfo, 450 ReplyBuffer, 451 ReplySize, 452 Info.Timeout); 453 } 454 else 455 { 456 (void)IcmpSendEcho2(Info.hIcmpFile, 457 NULL, 458 NULL, 459 NULL, 460 ((PSOCKADDR_IN)Info.Target->ai_addr)->sin_addr.s_addr, 461 SendBuffer, 462 (USHORT)PACKET_SIZE, 463 &IpOptionInfo, 464 ReplyBuffer, 465 ReplySize, 466 Info.Timeout); 467 } 468 469 if (DecodeResponse(ReplyBuffer, (Ping == NUM_OF_PINGS), FoundTarget) == false) 470 { 471 Quit = true; 472 break; 473 } 474 475 if (FoundTarget) 476 { 477 Success = true; 478 break; 479 } 480 } 481 482 HopCount++; 483 Sleep(100); 484 } 485 486 OutputText(IDS_TRACE_COMPLETE); 487 488 FreeAddrInfoW(Info.Target); 489 if (Info.hIcmpFile) 490 { 491 IcmpCloseHandle(Info.hIcmpFile); 492 } 493 494 return Success; 495 } 496 497 static bool 498 ParseCmdline(int argc, wchar_t *argv[]) 499 { 500 if (argc < 2) 501 { 502 Usage(); 503 return false; 504 } 505 506 for (int i = 1; i < argc; i++) 507 { 508 if (argv[i][0] == '-') 509 { 510 switch (argv[i][1]) 511 { 512 case 'd': 513 Info.ResolveAddresses = FALSE; 514 break; 515 516 case 'h': 517 Info.MaxHops = GetULONG(argv[++i]); 518 break; 519 520 case 'j': 521 printf("-j is not yet implemented.\n"); 522 return false; 523 524 case 'w': 525 Info.Timeout = GetULONG(argv[++i]); 526 break; 527 528 case '4': 529 Info.Family = AF_INET; 530 break; 531 532 case '6': 533 Info.Family = AF_INET6; 534 break; 535 536 default: 537 { 538 OutputText(IDS_INVALID_OPTION, argv[i]); 539 Usage(); 540 return false; 541 } 542 } 543 } 544 else 545 { 546 StringCchCopyW(Info.HostName, NI_MAXHOST, argv[i]); 547 break; 548 } 549 } 550 551 return true; 552 } 553 554 EXTERN_C 555 int wmain(int argc, wchar_t *argv[]) 556 { 557 Info.ResolveAddresses = true; 558 Info.MaxHops = 30; 559 Info.Timeout = 4000; 560 Info.Family = AF_UNSPEC; 561 562 if (!ParseCmdline(argc, argv)) 563 { 564 return 1; 565 } 566 567 WSADATA WsaData; 568 if (WSAStartup(MAKEWORD(2, 2), &WsaData)) 569 { 570 return 1; 571 } 572 573 bool Success; 574 Success = RunTraceRoute(); 575 576 WSACleanup(); 577 578 return Success ? 0 : 1; 579 } 580