Trace命令的实现
【trace原理】
traceroute是用来跟踪路由的命令,可以查看数据包从一端到另一端的路线。
当源执行traceroute的时候,第一个数据包的TTL设置为1,那么下一跳的路由器收到数据包之后会丢弃数据包,并且会向源发送一条错误信息,源通过阅读错误信息从而得知发送错误信息的路由器就是第一跳。源第二次发送数据包的时候把TTL的值设置为2,第二跳的路由器发送错误信息过来,源路由器就可以知道第二跳是谁。以此类推,直到发现目标为止。
【源码分析】
// trace.cpp : Defines the entry point forthe console application.
//
#include "stdafx.h"
#pragma pack(4)
/*加载头文件*/
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
/*定义常量*/
/*ICMP报文类型,回显请求*/
#define ICMP_ECHO 8
/*ICMP报文类型,回显应答*/
#define ICMP_ECHOREPLY 0
/*最大的ICMP数据报大小*/
#define MAX_PACKET 1024
/*最小的ICMP数据报大小*/
#define ICMP_MIN 8
/*默认数据报大小*/
#define DEF_PACKET_SIZE 32
#define STATUS_FAILED 0xFFFF
#define xmalloc(s)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(s))
#define xfree(p) HeapFree (GetProcessHeap(),0,(p))
/*IP报头字段数据结构*/
typedef struct iphdr {
unsignedint h_len:4; /*IP报头长度(4位)*/
unsignedint version:4; /*IP的版本号(4位)*/
unsignedchar tos; /*服务的类型(8位)*/
unsignedshort total_len; /*数据报总长度(16位)*/
unsignedshort ident; /*惟一的标识符(16位)*/
unsignedshort frag_and_flags; /*分段标志(3位)+分片偏移(13位)*/
unsignedchar ttl; /*生存期(8位)*/
unsignedchar proto; /*协议类型(TCP、UDP等)(8位)*/
unsignedshort checksum; /*校验和(16位)*/
unsignedint sourceIP; /*源IP地址(32位)*/
unsignedint destIP; /*目的IP地址(32位)*/
}IpHeader;
/*ICMP报头字段数据结构*/
typedef struct _icmphdr
{
BYTE i_type; /*ICMP报文类型(8位)*/
BYTE i_code; /*该类型中的代码号(8位)*/
USHORT i_cksum; /*校验和(16位)*/
USHORT i_id; /*惟一的标识符(16位)*/
USHORT i_seq; /*序列号(16位)*/
ULONG timestamp; /*时间戳(32位)*/
} IcmpHeader;
/*子函数声明*/
void fill_icmp_data(char *, int);
USHORT checksum(USHORT *, int);
void decode_resp(char *,int ,structsockaddr_in *);
void Usage(char *progname){
fprintf(stderr,"Usage:\n");
fprintf(stderr,"%s <host> [data_size]\n",progname);
fprintf(stderr,"datasize can be up to 1Kb\n");
ExitProcess(STATUS_FAILED);
}
int main(int argc, char **argv){
WSADATA wsaData;
SOCKET sockRaw;
struct sockaddr_in dest,from;
struct sockaddr_in *dest_tmp,*from_tmp;
struct hostent * hp;
intbread,datasize;
intfromlen = sizeof(from);
inttimeout = 1000;
intTTL=0;
char *dest_ip;
char *icmp_data;
char *recvbuf;
unsigned int addr=0;
USHORT seq_no = 0;
char temp1[100];
char temp2[100];
if(WSAStartup(MAKEWORD(2,2),&wsaData) != 0){
fprintf(stderr,"套接字初始化失败:%d\n",GetLastError());
ExitProcess(STATUS_FAILED);
}
if(argc <2 ) {
Usage(argv[0]);
}
sockRaw = WSASocket (AF_INET,
SOCK_RAW,
IPPROTO_ICMP,
NULL, 0,0);
if(sockRaw == INVALID_SOCKET) {
fprintf(stderr,"申请套接字失败:%d\n",WSAGetLastError());
ExitProcess(STATUS_FAILED);
}
TTL=1;//初始化TTL
bread =setsockopt(sockRaw,IPPROTO_IP,IP_TTL,(LPSTR)&TTL,sizeof(int));//设置原始套接字
if(bread == SOCKET_ERROR){
fprintf(stderr,"TTL设置:%d,错误:%d",bread,WSAGetLastError());
ExitProcess(STATUS_FAILED);
}
bread = setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,
sizeof(timeout));
if(bread == SOCKET_ERROR) {
fprintf(stderr,"接收超时设置失败:%d\n",WSAGetLastError());
ExitProcess(STATUS_FAILED);
}
timeout = 1000;
bread = setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,
sizeof(timeout));
if(bread == SOCKET_ERROR) {
fprintf(stderr,"发送超时设置失败:%d\n",WSAGetLastError());
ExitProcess(STATUS_FAILED);
}
memset(&dest,0,sizeof(dest));//复制目标地址
hp= gethostbyname(argv[1]);
if(!hp){
addr= inet_addr(argv[1]);
}
if((!hp) && (addr == INADDR_NONE)) {
fprintf(stderr,"无法解析%s\n",argv[1]);
ExitProcess(STATUS_FAILED);
}
if(hp != NULL)
memcpy(&(dest.sin_addr),hp->h_addr,hp->h_length);
else
dest.sin_addr.s_addr = addr;
if(hp)
dest.sin_family = hp->h_addrtype;
else
dest.sin_family = AF_INET;
dest_ip = inet_ntoa(dest.sin_addr);
if(argc >2) {
datasize= atoi(argv[2]);
if(datasize == 0)
datasize = DEF_PACKET_SIZE;
}
else
datasize = DEF_PACKET_SIZE;
datasize += sizeof(IcmpHeader);
icmp_data = xmalloc(MAX_PACKET);
recvbuf = xmalloc(MAX_PACKET);
if(!icmp_data) {
fprintf(stderr,"HeapAllocfailed %d\n",GetLastError());
ExitProcess(STATUS_FAILED);
}
memset(icmp_data,0,MAX_PACKET);
fill_icmp_data(icmp_data,datasize);
/*发送和接收数据*/
while(1) {
intbwrote;
bread= setsockopt(sockRaw,IPPROTO_IP,IP_TTL,(LPSTR)&TTL,sizeof(int));
((IcmpHeader*)icmp_data)->i_cksum= 0;
((IcmpHeader*)icmp_data)->timestamp= GetTickCount();
((IcmpHeader*)icmp_data)->i_seq= seq_no++;
((IcmpHeader*)icmp_data)->i_cksum= checksum((USHORT*)icmp_data,
datasize);
bwrote= sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,
sizeof(dest));
if(bwrote == SOCKET_ERROR){
if (WSAGetLastError() == WSAETIMEDOUT) {
printf("超时\n");
continue;
}
fprintf(stderr,"发送失败:%d\n",WSAGetLastError());
ExitProcess(STATUS_FAILED);
}
if(bwrote < datasize ) {
fprintf(stdout,"Wrote %dbytes\n",bwrote);
}
bread= recvfrom(sockRaw,recvbuf,MAX_PACKET,0,(struct sockaddr*)&from,
&fromlen);
if(bread == SOCKET_ERROR){
if (WSAGetLastError() == WSAETIMEDOUT) {
printf("超时\n");
continue;
}
fprintf(stderr,"接收失败:%d\n",WSAGetLastError());
ExitProcess(STATUS_FAILED);
}
/*TTL值加1*/
fprintf(stderr,"%d: ",TTL++);
decode_resp(recvbuf,bread,&from);
dest_tmp=&dest;
from_tmp=&from;
//fprintf(stderr,"dest.sin_addr.s_un:%s\n",inet_ntoa(dest_tmp->sin_addr));
//fprintf(stderr,"from.sin_addr.s_un:%s\n",inet_ntoa(from_tmp->sin_addr));
strcpy(temp1, inet_ntoa(dest_tmp->sin_addr)) ;
strcpy(temp2, inet_ntoa(from_tmp->sin_addr)) ;
//fprintf(stderr,"ddd:%d\n",(strcmp(inet_ntoa(from_tmp->sin_addr),inet_ntoa(dest_tmp->sin_addr))));
/*下一地址和目的地址*/
if(strcmp(temp1,temp2)==0)
{fprintf(stderr,"Trace命令完成!\n");
return1;}
Sleep(1000);
}
return 0;
}
/*解读ICMP报头函数*/
void decode_res(char *buf, int bytes,structsockaddr_in *from) {
IpHeader*iphdr;
IcmpHeader*icmphdr;
unsignedshort iphdrlen;
iphdr= (IpHeader *)buf;
// printf("RemoteTTL:%d\n",(int)iphdr->ttl);
iphdrlen= iphdr->h_len * 4 ; // number of 32-bit words *4 = bytes
/*如果读取的数据太小*/
if(bytes < iphdrlen + ICMP_MIN) {
printf("Toofew bytes from %s\n",inet_ntoa(from->sin_addr));
}
icmphdr= (IcmpHeader*)(buf + iphdrlen);
/*
/*如果收到的不是回显应答报文则报错*/
if(icmphdr->i_type != ICMP_ECHOREPLY) {
fprintf(stderr,"non-echotype %d recvd\n",icmphdr->i_type);
//return;
}
/*核实收到的ID号和发送的是否一致*/
if(icmphdr->i_id != (USHORT)GetCurrentProcessId()) {
fprintf(stderr,"someoneelse's packet!\n");
//return;
}
*/
printf("%dbytes %s ",bytes,inet_ntoa(from->sin_addr));
// printf("icmp_seq = %d. ",icmphdr->i_seq);
printf("time: %d ms ",GetTickCount()-icmphdr->timestamp);
printf("\n");
}
/*求校验和函数*/
USHORT checksum(USHORT *buffer, int size) {
unsigned long cksum=0;
while(size >1) {
cksum+=*buffer++;
size-=sizeof(USHORT);
}
if(size ) {
cksum+= *(UCHAR*)buffer;
}
/*对每个16bit进行二进制反码求和*/
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >>16);
return (USHORT)(~cksum);
}
/*填充ICMP数据报字段函数*/
void fill_icmp_data(char * icmp_data, intdatasize){
IcmpHeader *icmp_hdr;
char *datapart;
icmp_hdr = (IcmpHeader*)icmp_data;
/*ICMP报文类型设置为回显请求*/
icmp_hdr->i_type = ICMP_ECHO;
icmp_hdr->i_code = 0;
icmp_hdr->i_id = (USHORT)GetCurrentProcessId();
icmp_hdr->i_cksum = 0;
icmp_hdr->i_seq = 0;
datapart = icmp_data + sizeof(IcmpHeader);
/*以数字0填充剩余空间*/
memset(datapart,'E', datasize - sizeof(IcmpHeader));
}
【注意事项】
【字节对齐】
#pragma pack(push) //保存对齐状态
#pragma pack(4)//设定为4字节对齐
#pragma pack(pop)//恢复对齐状态
struct是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如array、struct、union等)的数据 单元。对于结构体,编译器会自动进行成员变量的对齐,以提高运算效率。
缺省情况下,编译器为结构体的每个成员按其自然对界(natural alignment)条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和 整个结构的地址相同。自然对界是指按结构体的成员中 sizeof 最大的成员对齐。
#pragma pack规定的对齐长度,实际使用的规则是:
1)结构,联合,或者类的数据成员,第一个放在偏移为0的地方,以后每个数据成员的对齐,按照#pragma pack指定的数值和结构体的自然对齐长度中比较小的那个进行。 就是说,当#pragma pack的值等于或超过所有数据成员长度的时候,这个值的大小将不产生任何效果。
2) 结构体的对齐,按照结构体中size最大的数据成员和#pragma pack指定值之间,较小的那个进行。
#pragma pack(4)
class TestC
{
public:
char a;//第一个成员,放在[0]偏移的位置,
short b;//第二个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[2,3]的位置。
char c;//第三个,自身长为1,放在[4]的位置。
};
整个类的大小是5字节,按照min(sizeof(short),4)字节对齐,也就是2字节对齐,结果是6。所以sizeof(TestC)是6。
windows下面默认的是#pragma pack(8)。所以如果要紧凑内存存放的话 需要用 #pragma pack(1)来限制了。
因为编译器在编译时会对程序进行优化,以便加快访问速度,所以一般都会按照2的倍数进行字节对齐。用这个宏就是为了防止编译器对结构的定义进行对齐。
#pragma(push,n)用来设置警告消息的等级
【error LNK2001】
出现link2001的错误的解决办法:
在project-> setting ->link ->object/librarymodules中添加:
ws2_32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.libadvapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.libodbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.libadvapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.libodbccp32.lib
就不会出现上面的错误了。