美文网首页
Windows下实现TraceRoute

Windows下实现TraceRoute

作者: 语文小子 | 来源:发表于2016-11-01 11:33 被阅读0次
    //IP数据报头
    typedef struct
    {
        unsigned char hdr_len : 4;  // 4位首部长度
        unsigned char version : 4;  // 4位版本号
        unsigned char tos;   // 8位服务类型
        unsigned short total_len;  // 16位总长度
        unsigned short identifier;  // 16位标识位
        unsigned short frag_and_flags; // flags
        unsigned char ttl;   // 8位生存时间(TTL)
        unsigned char protocol;  // 8位上层协议
        unsigned short checksum;  // 16位校验和
        unsigned long sourceIP;  // 源IP地址
        unsigned long destIP;   // 目标IP地址
    } IP_HEADER;
    
    //ICMP数据报头
    typedef struct
    {
        BYTE type;  //8位类型
        BYTE code;  //8位代码
        USHORT cksum;  //16位校验和
        USHORT id;   //16位标识符
        USHORT seq;  //16位序列号
    } ICMP_HEADER;
    

    Code:

    TraceRoute.h
    
    #pragma once
    
    #include <windows.h>
    
    #pragma pack(1)
    //IP数据报头
    typedef struct
    {
        unsigned char hdr_len : 4;  // 4位首部长度
        unsigned char version : 4;  // 4位版本号
        unsigned char tos;   // 8位服务类型
        unsigned short total_len;  // 16位总长度
        unsigned short identifier;  // 16位标识位
        unsigned short frag_and_flags; // flags
        unsigned char ttl;   // 8位生存时间(TTL)
        unsigned char protocol;  // 8位上层协议
        unsigned short checksum;  // 16位校验和
        unsigned long sourceIP;  // 源IP地址
        unsigned long destIP;   // 目标IP地址
    } IP_HEADER;
    
    //ICMP数据报头
    typedef struct
    {
        BYTE type;  //8位类型
        BYTE code;  //8位代码
        USHORT cksum;  //16位校验和
        USHORT id;   //16位标识符
        USHORT seq;  //16位序列号
    } ICMP_HEADER;
    
    //解码结果
    typedef struct
    {
        USHORT usSeqNo;   //包序列号
        DWORD dwRoundTripTime; //往返时间
        in_addr dwIPaddr;  //对端IP地址
    } DECODE_RESULT;
    #pragma pack()
    
    const BYTE ICMP_ECHO_REQUEST = 8; //请求回显
    const BYTE ICMP_ECHO_REPLY = 0; //回显应答
    const BYTE ICMP_TIMEOUT = 11; //传输超时
    
    const int DEF_MAX_HOP = 30;    //最大跳站数
    const DWORD DEF_ICMP_TIMEOUT = 3000; //默认超时时间,单位ms
    const int DEF_ICMP_DATA_SIZE = 32; //默认ICMP数据部分长度
    const int MAX_ICMP_PACKET_SIZE = 1024; //最大ICMP数据报的大小
    
    USHORT GenerateChecksum(USHORT* pBuf, int iSize);
    BOOL DecodeIcmpResponse(char* pBuf, int iPacketSize, DECODE_RESULT& stDecodeResult);
    
    TraceRoute.cpp
    
    #define _WINSOCK_DEPRECATED_NO_WARNINGS 
    
    #include <iostream>
    #include <ws2tcpip.h>
    #include <winsock2.h>
    #include <iomanip>
    #include "TraceRoute.h"
    
    #pragma comment(lib,"ws2_32")
    
    using namespace std;
    
    int main(int argc, char* argv[]) {
    
        //检查命令行参数
        if (argc != 2)
        {
            cerr << "\nUsage: itracert ip_or_hostname\n";
            return -1;
        }
        //初始化winsock2环境
        WSADATA wsa;
        if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
        {
            cerr << "\nFailed to initialize the WinSock2 DLL\n"
                << "error code: " << WSAGetLastError() << endl;
            return -1;
        }
    
        //将命令行参数转换为IP地址
        u_long ulDestIP = inet_addr(argv[1]);
        if (ulDestIP == INADDR_NONE){
    
            //转换不成功时按域名解析
            hostent* pHostent = gethostbyname(argv[1]);
            if (pHostent)
            {
                ulDestIP = (*(in_addr*)pHostent->h_addr).s_addr;
                //输出屏幕信息
                cout << "\nTracing route to " << argv[1]
                    << " [" << inet_ntoa(*(in_addr*)(&ulDestIP)) << "]"
                    << " with a maximum of " << DEF_MAX_HOP << " hops.\n" << endl;
            }
            else //解析主机名失败
            {
                cerr << "\nCould not resolve the host name " << argv[1] << '\n'
                    << "error code: " << WSAGetLastError() << endl;
                WSACleanup();
                return -1;
            }
        }
        else{
    
            //输出屏幕信息
            cout << "\nTracing route to " << argv[1]
                << " with a maximum of " << DEF_MAX_HOP << " hops.\n" << endl;
        }
    
        //填充目的Socket地址
        sockaddr_in destSockAddr;
        ZeroMemory(&destSockAddr, sizeof(sockaddr_in));
        destSockAddr.sin_family = AF_INET;
        destSockAddr.sin_addr.s_addr = ulDestIP;
        //使用ICMP协议创建Raw Socket
        SOCKET sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED);
        if (sockRaw == INVALID_SOCKET)
        {
            cerr << "\nFailed to create a raw socket\n"
                << "error code: " << WSAGetLastError() << endl;
            WSACleanup();
            return -1;
        }
    
        //设置端口属性
        int iTimeout = DEF_ICMP_TIMEOUT;
        if (setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&iTimeout, sizeof(iTimeout)) == SOCKET_ERROR)
        {
            cerr << "\nFailed to set recv timeout\n"
                << "error code: " << WSAGetLastError() << endl;
            closesocket(sockRaw);
            WSACleanup();
            return -1;
        }
        //创建ICMP包发送缓冲区和接收缓冲区
        char IcmpSendBuf[sizeof(ICMP_HEADER) + DEF_ICMP_DATA_SIZE];
        memset(IcmpSendBuf, 0, sizeof(IcmpSendBuf));
        char IcmpRecvBuf[MAX_ICMP_PACKET_SIZE];
        memset(IcmpRecvBuf, 0, sizeof(IcmpRecvBuf));
    
        //填充待发送的ICMP包
        ICMP_HEADER* pIcmpHeader = (ICMP_HEADER*)IcmpSendBuf;
        pIcmpHeader->type = ICMP_ECHO_REQUEST;
        pIcmpHeader->code = 0;
        pIcmpHeader->id = (USHORT)GetCurrentProcessId();
        memset(IcmpSendBuf + sizeof(ICMP_HEADER), 'E', DEF_ICMP_DATA_SIZE);
    
        //开始探测路由
        DECODE_RESULT stDecodeResult;
        BOOL bReachDestHost = FALSE;
        USHORT usSeqNo = 0;
        int iTTL = 1;
        int iMaxHop = DEF_MAX_HOP;
    
        while (!bReachDestHost && iMaxHop--) {
        
            //设置IP数据报头的ttl字段
            setsockopt(sockRaw, IPPROTO_IP, IP_TTL, (char*)&iTTL, sizeof(iTTL));
            //输出当前跳站数作为路由信息序号
            cout << setw(3) << iTTL << flush;
    
            //填充ICMP数据报剩余字段
            ((ICMP_HEADER*)IcmpSendBuf)->seq = htons(usSeqNo++);
            ((ICMP_HEADER*)IcmpSendBuf)->cksum = GenerateChecksum((USHORT*)IcmpSendBuf, sizeof(ICMP_HEADER) + DEF_ICMP_DATA_SIZE);
    
            //记录序列号和当前时间
            stDecodeResult.usSeqNo = ((ICMP_HEADER*)IcmpSendBuf)->seq;
            stDecodeResult.dwRoundTripTime = GetTickCount();
    
            //发送ICMP的EchoRequest数据报
            if (sendto(sockRaw, IcmpSendBuf, sizeof(IcmpSendBuf), 0,
                (sockaddr*)&destSockAddr, sizeof(destSockAddr)) == SOCKET_ERROR){
    
                //如果目的主机不可达则直接退出
                if (WSAGetLastError() == WSAEHOSTUNREACH)
                    cout << '\t' << "Destination host unreachable./n"
                    << "\nTrace complete.\n" << endl;
                closesocket(sockRaw);
                WSACleanup();
                return 0;
            }
    
            //接收ICMP的EchoReply数据报
            //因为收到的可能并非程序所期待的数据报,所以需要循环接收直到收到所要数据或超时
            sockaddr_in from;
            int iFromLen = sizeof(from);
            int iReadDataLen;
            while (1){
    
                //等待数据到达
                iReadDataLen = recvfrom(sockRaw, IcmpRecvBuf, MAX_ICMP_PACKET_SIZE,
                    0, (sockaddr*)&from, &iFromLen);
                if (iReadDataLen != SOCKET_ERROR) //有数据包到达
                {
                    //解码得到的数据包,如果解码正确则跳出接收循环发送下一个EchoRequest包
                    if (DecodeIcmpResponse(IcmpRecvBuf, iReadDataLen, stDecodeResult))
                    {
                        if (stDecodeResult.dwIPaddr.s_addr == destSockAddr.sin_addr.s_addr)
                            bReachDestHost = TRUE;
                        cout << '\t' << inet_ntoa(stDecodeResult.dwIPaddr) << endl;
                        break;
                    }
                }
                else if (WSAGetLastError() == WSAETIMEDOUT) //接收超时,打印星号
                {
                    cout << setw(9) << '*' << '\t' << "Request timed out." << endl;
                    break;
                }
                else
                {
                    cerr << "\nFailed to call recvfrom\n"
                        << "error code: " << WSAGetLastError() << endl;
                    closesocket(sockRaw);
                    WSACleanup();
                    return -1;
                }
            }
            //TTL值加1
            iTTL++;
        }
        //输出屏幕信息
        cout << "\nTrace complete.\n" << endl;
        closesocket(sockRaw);
        WSACleanup();
        return 0;
    }
    
    //产生网际校验和
    USHORT GenerateChecksum(USHORT* pBuf, int iSize)
    {
        unsigned long cksum = 0;
        while (iSize>1)
        {
            cksum += *pBuf++;
            iSize -= sizeof(USHORT);
        }
        if (iSize)
            cksum += *(UCHAR*)pBuf;
        cksum = (cksum >> 16) + (cksum & 0xffff);
        cksum += (cksum >> 16);
        return (USHORT)(~cksum);
    }
    
    
    //解码得到的数据报
    BOOL DecodeIcmpResponse(char* pBuf, int iPacketSize, DECODE_RESULT& stDecodeResult)
    {
        //检查数据报大小的合法性
        IP_HEADER* pIpHdr = (IP_HEADER*)pBuf;
        int iIpHdrLen = pIpHdr->hdr_len * 4;
        if (iPacketSize < (int)(iIpHdrLen + sizeof(ICMP_HEADER)))
            return FALSE;
        //按照ICMP包类型检查id字段和序列号以确定是否是程序应接收的Icmp包
        ICMP_HEADER* pIcmpHdr = (ICMP_HEADER*)(pBuf + iIpHdrLen);
        USHORT usID, usSquNo;
        if (pIcmpHdr->type == ICMP_ECHO_REPLY){
    
            usID = pIcmpHdr->id;
            usSquNo = pIcmpHdr->seq;
        }else if (pIcmpHdr->type == ICMP_TIMEOUT){
    
            char* pInnerIpHdr = pBuf + iIpHdrLen + sizeof(ICMP_HEADER);  //载荷中的IP头
            int iInnerIPHdrLen = ((IP_HEADER*)pInnerIpHdr)->hdr_len * 4;//载荷中的IP头长
            ICMP_HEADER* pInnerIcmpHdr = (ICMP_HEADER*)(pInnerIpHdr + iInnerIPHdrLen);//载荷中的ICMP头
            usID = pInnerIcmpHdr->id;
            usSquNo = pInnerIcmpHdr->seq;
        }else
            return FALSE;
    
        if (usID != (USHORT)GetCurrentProcessId() || usSquNo != stDecodeResult.usSeqNo)
            return FALSE;
        //处理正确收到的ICMP数据报
        if (pIcmpHdr->type == ICMP_ECHO_REPLY || pIcmpHdr->type == ICMP_TIMEOUT){
    
            //返回解码结果
            stDecodeResult.dwIPaddr.s_addr = pIpHdr->sourceIP;
            stDecodeResult.dwRoundTripTime = GetTickCount() - stDecodeResult.dwRoundTripTime;
            //打印屏幕信息
            if (stDecodeResult.dwRoundTripTime)
                cout << setw(6) << stDecodeResult.dwRoundTripTime << " ms" << flush;
            else
                cout << setw(6) << "<1" << " ms" << flush;
            return TRUE;
        }
        return FALSE;
    }
    

    相关文章

      网友评论

          本文标题:Windows下实现TraceRoute

          本文链接:https://www.haomeiwen.com/subject/bgtmuttx.html