·天新网首页·加入收藏·设为首页
首页|笔记本|手机|数码相机|摄像机|MP3/MP4|主板|内存|显示器|办公|打印机|下载|开发|汽车|学院|业界
硬件|台式机|数码|数字家庭|投影仪|GPS/CPU|显卡|硬盘|服务器|网络|一体机|驱动|源码|游戏|考试|报价
您现在的位置:天新网 > 软件开发 > 软件工程 > 软件工程
使用原始套接字实现时间戳程序
http://dev.21tx.com 2006年11月17日 天极网 极地圣火


  一、 什么是时间戳

  使用原始套接字实现的时间戳就是目标机器的当前时间。也就是说,可以通过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 时间戳程序运行界面

上一篇: 原始套接字透析之综合实例:网络黑手
下一篇: 原始套接字透析之实现包分析

英特尔 酷睿(TM)2双核,送指纹识别器一个,再赠两份好礼,请电800-858-2418

Google
 
热点文章
关于我们 | 联系我们 | 广告服务 | 工作机会 | 版权声明 | 欢迎投稿 | 网站地图
Copyright © 2000-2008 , www.21tx.com , All Rights Reserved .
晨新科技 版权所有 Created by TXSite.net