您的位置:首页 > 其它

TraceRoute(tracert)源码(基于原始套接字实现)

2008-11-04 20:17 573 查看
TraceRoute(tracert)源码(基于原始套接字实现) 佟强 2008.11.4

TraceRoute实现原理 http://blog.csdn.net/microtong/archive/2008/11/04/3220450.aspx



本程序实现Windows下tracert的功能,程序使用原始套接字发送ICMP回显请求报文,本接收ICMP超时差错报文,和ICMP回显应答报文,得到每一跳的路由器IP地址和往返时间。

//TraceRoute.h

#ifndef _ITRACERT_H_

#define _ITRACERT_H_

#pragma pack(1)

//IP数据报头

typedef struct

{

unsigned char hdr_len :4; // length of the header

unsigned char version :4; // version of IP

unsigned char tos; // type of service

unsigned short total_len; // total length of the packet

unsigned short identifier; // unique identifier

unsigned short frag_and_flags; // flags

unsigned char ttl; // time to live

unsigned char protocol; // protocol (TCP, UDP etc)

unsigned short checksum; // IP checksum

unsigned long sourceIP; // source IP address

unsigned long destIP; // destination IP address

} 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()

//ICMP类型字段

const BYTE ICMP_ECHO_REQUEST = 8; //请求回显

const BYTE ICMP_ECHO_REPLY = 0; //回显应答

const BYTE ICMP_TIMEOUT = 11; //传输超时

const DWORD DEF_ICMP_TIMEOUT = 3000; //默认超时时间,单位ms

const int DEF_ICMP_DATA_SIZE = 32; //默认ICMP数据部分长度

const int MAX_ICMP_PACKET_SIZE = 1024; //最大ICMP数据报的大小

const int DEF_MAX_HOP = 30; //最大跳站数

USHORT GenerateChecksum(USHORT* pBuf, int iSize);

BOOL DecodeIcmpResponse(char* pBuf, int iPacketSize, DECODE_RESULT& stDecodeResult);

#endif // _ITRACERT_H_

//TraceRoute.cpp

/*----------------------------------------------------------

功能说明:该程序简单实现了Windows操作系统的tracert命令功能,

可以输出IP报文从本机出发到达目的主机所经过的路由信息。

-----------------------------------------------------------*/

#include <iostream>

#include <iomanip>

#include <winsock2.h>

#include <ws2tcpip.h>

#include <stdio.h>

#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)->cksum = 0;

((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;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: