Windows下实现TraceRoute

//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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容