下载本文示例代码
推荐:经典教程专区 一、 什么是时间戳 使用原始套接字实现的时间戳就是目标机器的当前时间。也就是说,可以通过Raw Socket发送ICMP报文来查询目标机器的当前时间。不过通过这种方式所返回的时间并不是我们通过在时钟上看到的时、分、秒。而是直接以毫秒返回,返回的是统一的格林尼治时间。但这种ICMP报文也有它的优势,它可以提供毫秒级的分辨率,而通过其它方式查询时间,只能提供秒级的分辨率。 通过这种方式返回的时间是从午夜开始计算的毫秒时间,因此,它的值不会超过86,400,000(24*60*60*1000)。图1是ICMP时间戳请求和应答的报文格式。
图1 ICMP时间戳请求和应答报文 一般时间戳程序返回三个值,分别是发起时间戳(orig)、接收时间戳(recv)和发送时间戳(xmit)。但一般情况下,应答机器都将接收时间戳和发送时间戳设为同一个值。 二、 实现时间戳程序 在这一部分我们来通过C语言实现一个时间戳程序。首先让我们来定义一些常量。
#define ICMP_SEND 13 // 时间戳请求 #define ICMP_REPLY 14 // 时间戳回答#define ICMP_MIN 12 // ICMP的头字节最小在得低于12个字节#define STATUS_FAILED 0xFFFF#define MAX_PACKET 1024 #define xmalloc(s) HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(s))#define xfree(p) HeapFree(GetProcessHeap(),0,(p)) 由图1我们可以得知13表示请求报文,而14表示应答报文,这两个值分别使用ICMP_SEND和ICMP_REPLY定义。 接下来我们定义两个数据结构,一个是IP头,另一个是ICMP头。
/* IP 头结构,共20个字节 */typedef struct iphdr { unsigned char h_len:4; // 头长度 unsigned char version:4; // IP版本 unsigned char tos; // 服务类型 unsigned short total_len; // IP包的问长度 unsigned short ident; // 唯一标识 unsigned short frag_and_flags; // 标志 unsigned char ttl; // 往返时间 unsigned char proto; // 传输的协议(如TCP、UDP等) unsigned short checksum; // IP校验和 unsigned int sourceIP; // 源地址 unsigned int destIP; // 目标地址} IpHeader;/* ICMP头结构, 12个字节 */typedef struct _ihdr { BYTE i_type; BYTE i_code; USHORT i_cksum; USHORT i_id; USHORT i_seq; ULONG origtimestamp; //发起时间戳 ULONG recvtimestamp; //接收时间戳 ULONG xmittimestamp; //发送时间戳} IcmpHeader; 接下来让我们实现填充ICMP头结构的函数。
void fill_icmp_head(char * icmp_data){ IcmpHeader *icmp_hdr; icmp_hdr = (IcmpHeader*)icmp_data; icmp_hdr->i_type = ICMP_SEND; // 发送请求 icmp_hdr->i_code = 0; icmp_hdr->i_cksum = 0; icmp_hdr->i_id = (USHORT)GetCurrentProcessId(); icmp_hdr->i_seq = 0;} 下面是一个最重要的计算机校验和的函数。
USHORT checksum(USHORT *buffer, int size){ unsigned long cksum=0; // 初始化校验和为0 while(size >1) { cksum =*buffer ; size -=sizeof(USHORT); } if(size) { cksum = *(UCHAR*)buffer; } cksum = (cksum >> 16) (cksum & 0xffff); cksum = (cksum >>16); return (USHORT)(~cksum);} 下面这个函数用于显示目标机器返回来的时间戳信息。
void decode_resp(char *buf, int bytes,struct sockaddr_in *from){ IpHeader *iphdr; IcmpHeader *icmphdr; unsigned short iphdrlen; iphdr = (IpHeader *)buf; iphdrlen = iphdr->h_len * 4 ; // 32位相当于4个字节 if (bytes < iphdrlen ICMP_MIN) { printf("字节太少了 %s\n",inet_ntoa(from->sin_addr)); } icmphdr = (IcmpHeader*)(buf iphdrlen); if (icmphdr->i_type != ICMP_REPLY) { fprintf(stderr,"没有这个类型 %d recvd\n",icmphdr->i_type); return; } if (icmphdr->i_id != (USHORT)GetCurrentProcessId()) { fprintf(stderr,"错误!\n"); return ; } printf("%d bytes from %s:",bytes, inet_ntoa(from->sin_addr)); printf(" icmp_seq = %d. ",icmphdr->i_seq); printf(" orig: %d ms, recv: %d ms, xmit: %d ms ",icmphdr->origtimestamp, icmphdr->recvtimestamp, icmphdr->xmittimestamp); printf("\n");} 最后让我们来实现main函数。
int main(int argc, char* argv[]){ WSADATA wsaData; SOCKET sockRaw; struct sockaddr_in dest,from; struct hostent *hp; int bread,datasize; int fromlen = sizeof(from); char *dest_ip; char *icmp_data; char *recvbuf; unsigned int addr=0; USHORT seq_no = 0; // 选择Socket版本 if (WSAStartup(0x0101,&wsaData) != 0) { fprintf(stderr,"WSAStartup失败: %d\n",GetLastError()); ExitProcess(STATUS_FAILED); } if (argc <2 ) { Usage(argv[0]); } if((sockRaw=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP))==INVALID_SOCKE) { fprintf(stderr,"WSAStartup 失败: %d\n",GetLastError()); ExitProcess(STATUS_FAILED); } memset(&dest,0,sizeof(dest)); hp = gethostbyname(argv[1]); // 从参数中得到目标机器的IP if (hp!=NULL) { memcpy(&(dest.sin_addr),hp->h_addr,hp->h_length); dest.sin_family = AF_INET; dest_ip = inet_ntoa(dest.sin_addr); } else { fprintf(stderr,"IP地址不能为空 %s\n",argv[1]); ExitProcess(STATUS_FAILED); } datasize=sizeof(IcmpHeader); icmp_data = xmalloc(MAX_PACKET); recvbuf = xmalloc(MAX_PACKET); if (!icmp_data) { fprintf(stderr,"分配内存空间失败! %d\n",GetLastError()); ExitProcess(STATUS_FAILED); } memset(icmp_data,0,MAX_PACKET); fill_icmp_head(icmp_data); while(1) // 开始执行循环发送ICMP报文部分 { int bwrote; ((IcmpHeader*)icmp_data)->i_cksum = 0; ((IcmpHeader*)icmp_data)->origtimestamp = GetTickCount(); ((IcmpHeader*)icmp_data)->i_seq = seq_no ; ((IcmpHeader*)icmp_data)->i_cksum=checksum((USHORT*)icmp_data,sizeof(IcmpHeader)); // 开始向目标机器发送ICMP报文 bwrote = sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,sizeof(dest)); if (bwrote == SOCKET_ERROR) { fprintf(stderr,"sendto 失败: %d\n",WSAGetLastError()); ExitProcess(STATUS_FAILED); } if (bwrote < datasize ) { fprintf(stdout,"Wrote %d bytes\n",bwrote); } // 开始接收目标机器返回的信息 bread = recvfrom(sockRaw,recvbuf,MAX_PACKET,0,(struct sockaddr*)&from,&fromlen); if (bread == SOCKET_ERROR) { if (WSAGetLastError() == WSAETIMEDOUT) { printf("timed out\n"); continue; } fprintf(stderr,"recvfrom 失败: %d\n",WSAGetLastError()); perror("revffrom失败."); ExitProcess(STATUS_FAILED); } decode_resp(recvbuf,bread,&from); Sleep(1000); } closesocket(sockRaw); xfree(icmp_data); xfree(recvbuf); WSACleanup(); /* 清除 ws2_32.dll */ return 0;} 到现在为止,这个获得时间戳的程序已经完成了,读者可以下载本文提供的完成源代码进行更进一步地学习。图2是本程序运行界面。
图2 时间戳程序运行界面
推荐:经典教程专区 一、 什么是时间戳 使用原始套接字实现的时间戳就是目标机器的当前时间。也就是说,可以通过Raw Socket发送ICMP报文来查询目标机器的当前时间。不过通过这种方式所返回的时间并不是我们通过在时钟上看到的时、分、秒。而是直接以毫秒返回,返回的是统一的格林尼治时间。但这种ICMP报文也有它的优势,它可以提供毫秒级的分辨率,而通过其它方式查询时间,只能提供秒级的分辨率。 通过这种方式返回的时间是从午夜开始计算的毫秒时间,因此,它的值不会超过86,400,000(24*60*60*1000)。图1是ICMP时间戳请求和应答的报文格式。
图1 ICMP时间戳请求和应答报文 一般时间戳程序返回三个值,分别是发起时间戳(orig)、接收时间戳(recv)和发送时间戳(xmit)。但一般情况下,应答机器都将接收时间戳和发送时间戳设为同一个值。 二、 实现时间戳程序 在这一部分我们来通过C语言实现一个时间戳程序。首先让我们来定义一些常量。
#define ICMP_SEND 13 // 时间戳请求 #define ICMP_REPLY 14 // 时间戳回答#define ICMP_MIN 12 // ICMP的头字节最小在得低于12个字节#define STATUS_FAILED 0xFFFF#define MAX_PACKET 1024 #define xmalloc(s) HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(s))#define xfree(p) HeapFree(GetProcessHeap(),0,(p)) 由图1我们可以得知13表示请求报文,而14表示应答报文,这两个值分别使用ICMP_SEND和ICMP_REPLY定义。 接下来我们定义两个数据结构,一个是IP头,另一个是ICMP头。
/* IP 头结构,共20个字节 */typedef struct iphdr { unsigned char h_len:4; // 头长度 unsigned char version:4; // IP版本 unsigned char tos; // 服务类型 unsigned short total_len; // IP包的问长度 unsigned short ident; // 唯一标识 unsigned short frag_and_flags; // 标志 unsigned char ttl; // 往返时间 unsigned char proto; // 传输的协议(如TCP、UDP等) unsigned short checksum; // IP校验和 unsigned int sourceIP; // 源地址 unsigned int destIP; // 目标地址} IpHeader;/* ICMP头结构, 12个字节 */typedef struct _ihdr { BYTE i_type; BYTE i_code; USHORT i_cksum; USHORT i_id; USHORT i_seq; ULONG origtimestamp; //发起时间戳 ULONG recvtimestamp; //接收时间戳 ULONG xmittimestamp; //发送时间戳} IcmpHeader; 接下来让我们实现填充ICMP头结构的函数。
void fill_icmp_head(char * icmp_data){ IcmpHeader *icmp_hdr; icmp_hdr = (IcmpHeader*)icmp_data; icmp_hdr->i_type = ICMP_SEND; // 发送请求 icmp_hdr->i_code = 0; icmp_hdr->i_cksum = 0; icmp_hdr->i_id = (USHORT)GetCurrentProcessId(); icmp_hdr->i_seq = 0;} 下面是一个最重要的计算机校验和的函数。
USHORT checksum(USHORT *buffer, int size){ unsigned long cksum=0; // 初始化校验和为0 while(size >1) { cksum =*buffer ; size -=sizeof(USHORT); } if(size) { cksum = *(UCHAR*)buffer; } cksum = (cksum >> 16) (cksum & 0xffff); cksum = (cksum >>16); return (USHORT)(~cksum);} 下面这个函数用于显示目标机器返回来的时间戳信息。
void decode_resp(char *buf, int bytes,struct sockaddr_in *from){ IpHeader *iphdr; IcmpHeader *icmphdr; unsigned short iphdrlen; iphdr = (IpHeader *)buf; iphdrlen = iphdr->h_len * 4 ; // 32位相当于4个字节 if (bytes < iphdrlen ICMP_MIN) { printf("字节太少了 %s\n",inet_ntoa(from->sin_addr)); } icmphdr = (IcmpHeader*)(buf iphdrlen); if (icmphdr->i_type != ICMP_REPLY) { fprintf(stderr,"没有这个类型 %d recvd\n",icmphdr->i_type); return; } if (icmphdr->i_id != (USHORT)GetCurrentProcessId()) { fprintf(stderr,"错误!\n"); return ; } printf("%d bytes from %s:",bytes, inet_ntoa(from->sin_addr)); printf(" icmp_seq = %d. ",icmphdr->i_seq); printf(" orig: %d ms, recv: %d ms, xmit: %d ms ",icmphdr->origtimestamp, icmphdr->recvtimestamp, icmphdr->xmittimestamp); printf("\n");} 最后让我们来实现main函数。
int main(int argc, char* argv[]){ WSADATA wsaData; SOCKET sockRaw; struct sockaddr_in dest,from; struct hostent *hp; int bread,datasize; int fromlen = sizeof(from); char *dest_ip; char *icmp_data; char *recvbuf; unsigned int addr=0; USHORT seq_no = 0; // 选择Socket版本 if (WSAStartup(0x0101,&wsaData) != 0) { fprintf(stderr,"WSAStartup失败: %d\n",GetLastError()); ExitProcess(STATUS_FAILED); } if (argc <2 ) { Usage(argv[0]); } if((sockRaw=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP))==INVALID_SOCKE) { fprintf(stderr,"WSAStartup 失败: %d\n",GetLastError()); ExitProcess(STATUS_FAILED); } memset(&dest,0,sizeof(dest)); hp = gethostbyname(argv[1]); // 从参数中得到目标机器的IP if (hp!=NULL) { memcpy(&(dest.sin_addr),hp->h_addr,hp->h_length); dest.sin_family = AF_INET; dest_ip = inet_ntoa(dest.sin_addr); } else { fprintf(stderr,"IP地址不能为空 %s\n",argv[1]); ExitProcess(STATUS_FAILED); } datasize=sizeof(IcmpHeader); icmp_data = xmalloc(MAX_PACKET); recvbuf = xmalloc(MAX_PACKET); if (!icmp_data) { fprintf(stderr,"分配内存空间失败! %d\n",GetLastError()); ExitProcess(STATUS_FAILED); } memset(icmp_data,0,MAX_PACKET); fill_icmp_head(icmp_data); while(1) // 开始执行循环发送ICMP报文部分 { int bwrote; ((IcmpHeader*)icmp_data)->i_cksum = 0; ((IcmpHeader*)icmp_data)->origtimestamp = GetTickCount(); ((IcmpHeader*)icmp_data)->i_seq = seq_no ; ((IcmpHeader*)icmp_data)->i_cksum=checksum((USHORT*)icmp_data,sizeof(IcmpHeader)); // 开始向目标机器发送ICMP报文 bwrote = sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,sizeof(dest)); if (bwrote == SOCKET_ERROR) { fprintf(stderr,"sendto 失败: %d\n",WSAGetLastError()); ExitProcess(STATUS_FAILED); } if (bwrote < datasize ) { fprintf(stdout,"Wrote %d bytes\n",bwrote); } // 开始接收目标机器返回的信息 bread = recvfrom(sockRaw,recvbuf,MAX_PACKET,0,(struct sockaddr*)&from,&fromlen); if (bread == SOCKET_ERROR) { if (WSAGetLastError() == WSAETIMEDOUT) { printf("timed out\n"); continue; } fprintf(stderr,"recvfrom 失败: %d\n",WSAGetLastError()); perror("revffrom失败."); ExitProcess(STATUS_FAILED); } decode_resp(recvbuf,bread,&from); Sleep(1000); } closesocket(sockRaw); xfree(icmp_data); xfree(recvbuf); WSACleanup(); /* 清除 ws2_32.dll */ return 0;} 到现在为止,这个获得时间戳的程序已经完成了,读者可以下载本文提供的完成源代码进行更进一步地学习。图2是本程序运行界面。
图2 时间戳程序运行界面
下载本文示例代码
使用原始套接字实现时间戳程序使用原始套接字实现时间戳程序使用原始套接字实现时间戳程序使用原始套接字实现时间戳程序使用原始套接字实现时间戳程序使用原始套接字实现时间戳程序使用原始套接字实现时间戳程序使用原始套接字实现时间戳程序使用原始套接字实现时间戳程序使用原始套接字实现时间戳程序使用原始套接字实现时间戳程序使用原始套接字实现时间戳程序使用原始套接字实现时间戳程序使用原始套接字实现时间戳程序使用原始套接字实现时间戳程序