/*++ Copyright (c) 1991-2000 Microsoft Corporation Module Name: tracert.c Abstract: TraceRoute utility for TCP/IP. Author: Numerous TCP/IP folks. Revision History: Who When What -------- -------- ---------------------------------------------- Notes: --*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "llinfo.h" #include "tcpcmd.h" #include "ipexport.h" #include "icmpapi.h" #include "nlstxt.h" #define DEFAULT_MAXIMUM_HOPS 30 #define DEFAULT_TOS 0 #define DEFAULT_FLAGS 0 #define DEFAULT_SEND_SIZE 64 #define DEFAULT_RECEIVE_SIZE ( (sizeof(ICMP_ECHO_REPLY) + \ DEFAULT_SEND_SIZE + \ MAX_OPT_SIZE)) #define DEFAULT_TOS 0 #define DEFAULT_TIMEOUT 4000L #define MIN_INTERVAL 1000L #define STDOUT 1 char SendBuffer[DEFAULT_SEND_SIZE]; char RcvBuffer[DEFAULT_RECEIVE_SIZE]; WSADATA WsaData; struct IPErrorTable { IP_STATUS Error; // The IP Error DWORD ErrorNlsID; // The corresponding NLS string ID. } ErrorTable[] = { { IP_BUF_TOO_SMALL, TRACERT_BUF_TOO_SMALL }, { IP_DEST_NET_UNREACHABLE, TRACERT_DEST_NET_UNREACHABLE }, { IP_DEST_HOST_UNREACHABLE, TRACERT_DEST_HOST_UNREACHABLE }, { IP_DEST_PROT_UNREACHABLE, TRACERT_DEST_PROT_UNREACHABLE }, { IP_DEST_PORT_UNREACHABLE, TRACERT_DEST_PORT_UNREACHABLE }, { IP_NO_RESOURCES, TRACERT_NO_RESOURCES }, { IP_BAD_OPTION, TRACERT_BAD_OPTION }, { IP_HW_ERROR, TRACERT_HW_ERROR }, { IP_PACKET_TOO_BIG, TRACERT_PACKET_TOO_BIG }, { IP_REQ_TIMED_OUT, TRACERT_REQ_TIMED_OUT }, { IP_BAD_REQ, TRACERT_BAD_REQ }, { IP_BAD_ROUTE, TRACERT_BAD_ROUTE }, { IP_TTL_EXPIRED_TRANSIT, TRACERT_TTL_EXPIRED_TRANSIT }, { IP_TTL_EXPIRED_REASSEM, TRACERT_TTL_EXPIRED_REASSEM }, { IP_PARAM_PROBLEM, TRACERT_PARAM_PROBLEM }, { IP_SOURCE_QUENCH, TRACERT_SOURCE_QUENCH }, { IP_OPTION_TOO_BIG, TRACERT_OPTION_TOO_BIG }, { IP_BAD_DESTINATION, TRACERT_BAD_DESTINATION }, { IP_NEGOTIATING_IPSEC, TRACERT_NEGOTIATING_IPSEC }, { IP_GENERAL_FAILURE, TRACERT_GENERAL_FAILURE } }; PWCHAR GetErrorString(int ErrorCode) { DWORD Status; DWORD Length; static WCHAR ErrorString[2048]; // a 2K static buffer should suffice Length = 2048; Status = GetIpErrorString(ErrorCode, ErrorString, &Length); if (Status == NO_ERROR) { return ErrorString; // success } return L""; // return a null string } unsigned NlsPutMsg(unsigned Handle, unsigned usMsgNum, ... ) { unsigned msglen; VOID * vp; va_list arglist; DWORD StrLen; va_start(arglist, usMsgNum); if ((msglen = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE, NULL, usMsgNum, 0L, // Default country ID. (LPTSTR)&vp, 0, &arglist)) == 0) return(0); // Convert vp to oem StrLen=(DWORD)strlen(vp); CharToOemBuff((LPCTSTR)vp,(LPSTR)vp,StrLen); msglen = _write(Handle, vp, StrLen); LocalFree(vp); return(msglen); } unsigned long str2ip(char *addr) { char *endptr; int i; // Counter variable. unsigned long curaddr = 0; unsigned long temp; for (i = 0; i < 4; i++) { temp = strtoul(addr, &endptr, 10); if (temp > 255) return 0L; if (endptr[0] != '.') if (i != 3) return 0L; else if (endptr[0] != '\0' && endptr[0] != ' ') return 0L; addr = endptr+1; curaddr = (curaddr << 8) + temp; } return net_long(curaddr); } void print_addr(SOCKADDR *sa, socklen_t salen, BOOLEAN DoReverseLookup) { char hostname[NI_MAXHOST]; int i; BOOLEAN didReverse = FALSE; if (DoReverseLookup) { i = getnameinfo(sa, salen, hostname, sizeof(hostname), NULL, 0, NI_NAMEREQD); if (i == NO_ERROR) { didReverse = TRUE; NlsPutMsg(STDOUT, TRACERT_TARGET_NAME, hostname); } } i = getnameinfo(sa, salen, hostname, sizeof(hostname), NULL, 0, NI_NUMERICHOST); if (i != NO_ERROR) { // This should never happen unless there is a memory problem, // in which case the message associated with TRACERT_NO_RESOURCES // is reasonable. NlsPutMsg(STDOUT, TRACERT_NO_RESOURCES); exit (1); } if (didReverse) { NlsPutMsg( STDOUT, TRACERT_BRKT_IP_ADDRESS, hostname ); } else { NlsPutMsg( STDOUT, TRACERT_IP_ADDRESS, hostname ); } } void print_ip_addr(IPAddr ipv4Addr, BOOLEAN DoReverseLookup) { SOCKADDR_IN sin; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = ipv4Addr; print_addr((LPSOCKADDR)&sin, sizeof(sin), DoReverseLookup); } void print_ipv6_addr(IN6_ADDR *ipv6Addr, BOOLEAN DoReverseLookup) { SOCKADDR_IN6 sin; memset(&sin, 0, sizeof(sin)); sin.sin6_family = AF_INET6; memcpy(&sin.sin6_addr, ipv6Addr, sizeof(sin.sin6_addr)); print_addr((LPSOCKADDR)&sin, sizeof(sin), DoReverseLookup); } void print_time(ulong Time) { if (Time) { NlsPutMsg( STDOUT, TRACERT_TIME, Time ); } else { NlsPutMsg( STDOUT, TRACERT_TIME_10MS ); } } BOOLEAN param( ulong *parameter, char **argv, int argc, int current, ulong min, ulong max ) { ulong temp; char *dummy; if (current == (argc - 1) ) { NlsPutMsg( STDOUT, TRACERT_NO_OPTION_VALUE, argv[current] ); return(FALSE); } temp = strtoul(argv[current+1], &dummy, 0); if (temp < min || temp > max) { NlsPutMsg( STDOUT, TRACERT_BAD_OPTION_VALUE, argv[current] ); return(FALSE); } *parameter = temp; return(TRUE); } BOOLEAN ResolveTarget( int Family, char *TargetString, SOCKADDR *TargetAddress, socklen_t *TargetAddressLen, char *TargetName, int TargetNameLen, BOOLEAN DoReverseLookup ) { int i; struct addrinfo hints, *ai; TargetName[0] = '\0'; memset(&hints, 0, sizeof(hints)); hints.ai_family = Family; hints.ai_flags = AI_NUMERICHOST; i = getaddrinfo(TargetString, NULL, &hints, &ai); if (i == NO_ERROR) { *TargetAddressLen = (socklen_t)ai->ai_addrlen; memcpy(TargetAddress, ai->ai_addr, ai->ai_addrlen); if (DoReverseLookup) { getnameinfo(ai->ai_addr, (socklen_t)ai->ai_addrlen, TargetName, TargetNameLen, NULL, 0, NI_NAMEREQD); } freeaddrinfo(ai); return(TRUE); } else { hints.ai_flags = AI_CANONNAME; if (getaddrinfo(TargetString, NULL, &hints, &ai) == 0) { *TargetAddressLen = (socklen_t)ai->ai_addrlen; memcpy(TargetAddress, ai->ai_addr, ai->ai_addrlen); strcpy(TargetName, (ai->ai_canonname)? ai->ai_canonname : TargetString); freeaddrinfo(ai); return(TRUE); } } return(FALSE); } // ResolveTarget int GetSource(int family, char *astr, struct sockaddr *address) { struct addrinfo hints; struct addrinfo *result; memset(&hints, 0, sizeof hints); hints.ai_flags = AI_PASSIVE; hints.ai_family = family; if (getaddrinfo(astr, NULL, &hints, &result) != 0) return FALSE; RtlCopyMemory(address, result->ai_addr, result->ai_addrlen); return TRUE; } BOOLEAN SetFamily(DWORD *Family, DWORD Value, char *arg) { if ((*Family != AF_UNSPEC) && (*Family != Value)) { NlsPutMsg(STDOUT, TRACERT_FAMILY, arg, (Value==AF_INET)? "IPv4" : "IPv6"); return FALSE; } *Family = Value; return TRUE; } int __cdecl main(int argc, char **argv) { SOCKADDR_STORAGE address, sourceAddress; socklen_t addressLen; IPAddr reply4Address = INADDR_ANY; IN6_ADDR reply6Address; DWORD numberOfReplies; HANDLE IcmpHandle; DWORD status; PICMP_ECHO_REPLY reply4; PICMPV6_ECHO_REPLY reply6; char hostname[NI_MAXHOST], literal[INET6_ADDRSTRLEN]; char *arg; int i; ulong maximumHops = DEFAULT_MAXIMUM_HOPS; BOOLEAN doReverseLookup = TRUE; IP_OPTION_INFORMATION options; uchar optionsData[MAX_OPT_SIZE]; uchar *optionPtr; uchar currentIndex; IPAddr tempAddr; uchar j; uchar SRIndex = 0; ulong timeout = DEFAULT_TIMEOUT; BOOLEAN foundAddress = FALSE; BOOLEAN haveReply; DWORD Family = AF_UNSPEC; // // This will ensure the correct language message is displayed when // NlsPutMsg is called. // SetThreadUILanguage(0); if (WSAStartup(MAKEWORD(2, 0), &WsaData)) { NlsPutMsg(STDOUT, TRACERT_WSASTARTUP_FAILED, GetLastError()); return(1); } memset(&sourceAddress, 0, sizeof sourceAddress); memset(&address, 0, sizeof address); addressLen = sizeof(address); options.Ttl = 1; options.Tos = DEFAULT_TOS; options.Flags = DEFAULT_FLAGS; options.OptionsSize = 0; options.OptionsData = optionsData; if (argc < 2) { NlsPutMsg( STDOUT, TRACERT_USAGE, argv[0] ); goto error_exit; } // // process command line // for (i=1; i < argc; i++) { arg = argv[i]; if ((arg[0] == '-') || (arg[0] == '/')) { switch(arg[1]) { case '?': NlsPutMsg(STDOUT, TRACERT_USAGE, argv[0]); goto error_exit; case 'd': doReverseLookup = FALSE; break; case 'h': if (!param(&maximumHops, argv, argc, i++, 1, 255)) { goto error_exit; } break; case 'j': // Loose source routing // Only implemented for IPv4 so far if (!SetFamily(&Family, AF_INET, arg)) { goto error_exit; } currentIndex = options.OptionsSize; if ((currentIndex + 7) > MAX_OPT_SIZE) { NlsPutMsg(STDOUT, TRACERT_TOO_MANY_OPTIONS); goto error_exit; } optionPtr = options.OptionsData; optionPtr[currentIndex] = (uchar) IP_OPT_LSRR; optionPtr[currentIndex+1] = 3; optionPtr[currentIndex+2] = 4; // Set initial pointer value options.OptionsSize += 3; while ( (i < (argc - 2)) && (*argv[i+1] != '-')) { if ((currentIndex + 7) > MAX_OPT_SIZE) { NlsPutMsg(STDOUT, TRACERT_TOO_MANY_OPTIONS); goto error_exit; } arg = argv[++i]; tempAddr = inet_addr(arg); if (tempAddr == INADDR_NONE) { NlsPutMsg( STDOUT, TRACERT_BAD_ROUTE_ADDRESS, arg ); goto error_exit; } j = optionPtr[currentIndex+1]; *(ulong UNALIGNED *)&optionPtr[j+currentIndex] = tempAddr; optionPtr[currentIndex+1] += 4; options.OptionsSize += 4; } SRIndex = optionPtr[currentIndex+1] + currentIndex; optionPtr[currentIndex+1] += 4; // Save space for dest. addr options.OptionsSize += 4; break; case 'w': if (!param(&timeout, argv, argc, i++, 1, 0xffffffff)) { goto error_exit; } break; case 'R': // Only implemented for IPv6 so far if (!SetFamily(&Family, AF_INET6, arg)) { goto error_exit; } options.Flags |= ICMPV6_ECHO_REQUEST_FLAG_REVERSE; break; case 'S': // Only implemented for IPv6 so far if (!SetFamily(&Family, AF_INET6, arg)) { goto error_exit; } if (!GetSource(Family, argv[++i], (LPSOCKADDR)&sourceAddress)) { NlsPutMsg(STDOUT, TRACERT_BAD_ADDRESS, argv[i]); goto error_exit; } break; case '4': if (!SetFamily(&Family, AF_INET, arg)) { goto error_exit; } break; case '6': if (!SetFamily(&Family, AF_INET6, arg)) { goto error_exit; } break; default: NlsPutMsg(STDOUT, TRACERT_INVALID_SWITCH, argv[i]); NlsPutMsg(STDOUT, TRACERT_USAGE); goto error_exit; break; } } else { foundAddress = TRUE; if (!ResolveTarget(Family, argv[i], (LPSOCKADDR)&address, &addressLen, hostname, sizeof(hostname), doReverseLookup)) { NlsPutMsg( STDOUT, TRACERT_MESSAGE_1, argv[i] ); goto error_exit; } } } if (!foundAddress) { NlsPutMsg(STDOUT, TRACERT_NO_ADDRESS); NlsPutMsg(STDOUT, TRACERT_USAGE); goto error_exit; } Family = address.ss_family; if (Family == AF_INET) { if (SRIndex != 0) { *(ulong UNALIGNED *)&options.OptionsData[SRIndex] = ((LPSOCKADDR_IN)&address)->sin_addr.s_addr; } IcmpHandle = IcmpCreateFile(); } else { if (sourceAddress.ss_family == AF_UNSPEC) { SOCKET s; DWORD BytesReturned; // // A source address was not specified. // Get the preferred source address for this destination. // // If you want each individual echo request // to select a source address, use "-S ::". // s = socket(address.ss_family, 0, 0); if (s == INVALID_SOCKET) { NlsPutMsg(STDOUT, TRACERT_SOCKET_FAILED, WSAGetLastError()); exit(1); } (void) WSAIoctl(s, SIO_ROUTING_INTERFACE_QUERY, &address, sizeof address, &sourceAddress, sizeof sourceAddress, &BytesReturned, NULL, NULL); closesocket(s); } IcmpHandle = Icmp6CreateFile(); } if (IcmpHandle == INVALID_HANDLE_VALUE) { status = GetLastError(); NlsPutMsg( STDOUT, TRACERT_MESSAGE_2, status ); goto error_exit; } getnameinfo((LPSOCKADDR)&address, addressLen, literal, sizeof(literal), NULL, 0, NI_NUMERICHOST); if (hostname[0]) { NlsPutMsg( STDOUT, TRACERT_HEADER1, hostname, literal, maximumHops ); } else { NlsPutMsg( STDOUT, TRACERT_HEADER2, literal, maximumHops ); } while((options.Ttl <= maximumHops) && (options.Ttl != 0)) { NlsPutMsg( STDOUT, TRACERT_MESSAGE_4, (uint)options.Ttl ); haveReply = FALSE; for (i=0; i<3; i++) { BOOLEAN ErrorNotHandled = FALSE; if (Family == AF_INET) { numberOfReplies = IcmpSendEcho2( IcmpHandle, 0, NULL, NULL, ((LPSOCKADDR_IN)&address)->sin_addr.s_addr, SendBuffer, DEFAULT_SEND_SIZE, &options, RcvBuffer, DEFAULT_RECEIVE_SIZE, timeout ); if (numberOfReplies == 0) { // We did not get any replies. This is possibly a timeout, // or an internal error to IP. // status = GetLastError(); reply4 = NULL; if (status == IP_REQ_TIMED_OUT) { NlsPutMsg(STDOUT, TRACERT_NO_REPLY_TIME); if (i == 2) { if (haveReply) { print_ip_addr( reply4Address, doReverseLookup ); NlsPutMsg(STDOUT, TRACERT_CR); } else { NlsPutMsg( STDOUT, TRACERT_REQ_TIMED_OUT); } } } else { ErrorNotHandled = TRUE; } } else { // We got a reply. It's either for the final destination // (IP_SUCCESS), or because the TTL expired at a node along // the way, or we got an unexpected error response. // reply4 = (PICMP_ECHO_REPLY) RcvBuffer; status = reply4->Status; if (status == IP_SUCCESS) { print_time(reply4->RoundTripTime); if (i == 2) { print_ip_addr( reply4->Address, doReverseLookup ); NlsPutMsg(STDOUT, TRACERT_CR); goto loop_end; } else { haveReply = TRUE; reply4Address = reply4->Address; } } else if (status == IP_TTL_EXPIRED_TRANSIT) { print_time(reply4->RoundTripTime); if (i == 2) { print_ip_addr( reply4->Address, doReverseLookup ); NlsPutMsg(STDOUT, TRACERT_CR); if (reply4->RoundTripTime < MIN_INTERVAL) { Sleep(MIN_INTERVAL - reply4->RoundTripTime); } } else { haveReply = TRUE; reply4Address = reply4->Address; } } else { ErrorNotHandled = TRUE; } } // If we've not handled the status code by now, it represents // an unexpected fatal error and we'll now bail out. // if (ErrorNotHandled) { if (status < IP_STATUS_BASE) { NlsPutMsg( STDOUT, TRACERT_MESSAGE_7, status ); } else { if (reply4 != NULL) { print_ip_addr( reply4->Address, doReverseLookup ); NlsPutMsg( STDOUT, TRACERT_MESSAGE_6 ); } for (i = 0; ( ErrorTable[i].Error != status && ErrorTable[i].Error != IP_GENERAL_FAILURE ); i++ ); NlsPutMsg( STDOUT, ErrorTable[i].ErrorNlsID ); } goto loop_end; } } else { // AF_INET6 numberOfReplies = Icmp6SendEcho2( IcmpHandle, 0, NULL, NULL, (LPSOCKADDR_IN6)&sourceAddress, (LPSOCKADDR_IN6)&address, SendBuffer, DEFAULT_SEND_SIZE, &options, RcvBuffer, DEFAULT_RECEIVE_SIZE, timeout ); if (numberOfReplies == 0) { // We did not get any replies. This is possibly a timeout, // or an internal error to IP. // status = GetLastError(); reply6 = NULL; if (status == IP_REQ_TIMED_OUT) { NlsPutMsg(STDOUT, TRACERT_NO_REPLY_TIME); if (i == 2) { if (haveReply) { print_ipv6_addr( &reply6Address, doReverseLookup ); NlsPutMsg(STDOUT, TRACERT_CR); } else { NlsPutMsg( STDOUT, TRACERT_REQ_TIMED_OUT); } } } else { ErrorNotHandled = TRUE; } } else { // We got a reply. It's either for the final destination // (IP_SUCCESS), or because the TTL expired at a node along // the way, or we got an unexpected error response. // reply6 = (PICMPV6_ECHO_REPLY) RcvBuffer; status = reply6->Status; if (status == IP_SUCCESS) { print_time(reply6->RoundTripTime); if (i == 2) { print_ipv6_addr( (IN6_ADDR*)&reply6->Address.sin6_addr, doReverseLookup ); NlsPutMsg(STDOUT, TRACERT_CR); goto loop_end; } else { haveReply = TRUE; RtlCopyMemory(&reply6Address, &reply6->Address.sin6_addr, sizeof(IN6_ADDR)); } } else if (status == IP_HOP_LIMIT_EXCEEDED) { print_time(reply6->RoundTripTime); if (i == 2) { print_ipv6_addr( (IN6_ADDR*)&reply6->Address.sin6_addr, doReverseLookup ); NlsPutMsg(STDOUT, TRACERT_CR); if (reply6->RoundTripTime < MIN_INTERVAL) { Sleep(MIN_INTERVAL - reply6->RoundTripTime); } } else { haveReply = TRUE; RtlCopyMemory(&reply6Address, &reply6->Address.sin6_addr, sizeof(IN6_ADDR)); } } else { ErrorNotHandled = TRUE; } } // If we've not handled the status code by now, it represents // an unexpected fatal error and we'll now bail out. // if (ErrorNotHandled) { if (status < IP_STATUS_BASE) { NlsPutMsg( STDOUT, TRACERT_MESSAGE_7, status ); } else { if (reply6 != NULL) { print_ipv6_addr( (IN6_ADDR*)&reply6->Address.sin6_addr, doReverseLookup ); NlsPutMsg( STDOUT, TRACERT_MESSAGE_6 ); } for (i = 0; ( ErrorTable[i].Error != status && ErrorTable[i].Error != IP_GENERAL_FAILURE ); i++ ); NlsPutMsg( STDOUT, ErrorTable[i].ErrorNlsID ); } goto loop_end; } } } options.Ttl++; } loop_end: NlsPutMsg( STDOUT, TRACERT_MESSAGE_8 ); IcmpCloseHandle(IcmpHandle); WSACleanup(); return(0); error_exit: WSACleanup(); return(1); }