| 网站首页 | 模板 | 资料 | 源码 | 工具 | 开发 | 设计 | 安全 | 项目 | 网络 | 图片 | 系统 | 数据库 | 博客 | 会员中心 | 小说 | 
MYFTP 精品资料下载
网络学院
学习资料
源码模版
您现在的位置: 精品下载 >> 数据库 >> 数据库原理 >> 数据库正文 用户登录 新用户注册
原始套接字透析之实现Ping          【字体:
原始套接字透析之实现Ping
作者:佚名    数据库来源:不详    点击数:    更新时间:2008-6-27

  极其常用的Ping命令通过向计算机发送ICMP Echo请求报文并且监听回应报文的返回,以校验与远程计算机或本地计算机的连接。

  使用ICMP.DLL实现Ping

  在Windows平台编程中实现Ping的一个最简单方法是调用ICMP.DLL这个动态链接库,引用ICMP.DLL中的三个函数即可:

HANDLE IcmpCreateFile(void);
  这个函数打开个ICMP Echo请求能使用的句柄;

BOOL IcmpCloseHandle(HANDLE IcmpHandle);
  这个函数关闭由IcmpCreateFile打开的句柄;

DWord IcmpSendEcho(
 HANDLE IcmpHandle, // IcmpCreateFile打开的句柄
 IPAddr DestinationAddress, //Echo请求的目的地址
 LPVOID RequestData, //发送数据buffer
 WORD RequestSize, //发送数据长度
 PIP_OPTION_INFORMATION RequestOptions, // IP_OPTION_INFORMATION指针
 LPVOID ReplyBuffer, //接收回复buffer
 DWORD ReplySize, //接收回复buffer大小
 DWORD Timeout //等待超时
);
  这个函数发送Echo请求并等待回复或超时。

  把这个函数和相关数据封装成一个类CPing,CPing类的头文件如下:

class CPing
{
 public:
  CPing();
  ~CPing();
  BOOL Ping(char* strHost);
 private:
  // ICMP.DLL 导出函数指针
  HANDLE (WINAPI *pIcmpCreateFile)(VOID);
  BOOL (WINAPI *pIcmpCloseHandle)(HANDLE);
  DWORD (WINAPI *pIcmpSendEcho)(HANDLE,DWORD,LPVOID,WORD,PIPINFO,LPVOID,DWORD,DWORD);
  HANDLE hndlIcmp; // 加载ICMP.DLL库句柄
  BOOL bValid; //是否构造(获得ICMP.DLL导出函数指针和初始化WinSock)成功
};
  CPing类的构造函数获得ICMP.DLL中导出函数的指针并初始化WinSock:

CPing::CPing()
{
 bValid = FALSE;
 WSADATA wsaData;
 int nRet;
 // 动态加载ICMP.DLL
 hndlIcmp = LoadLibrary("ICMP.DLL");
 if (hndlIcmp == NULL)
 {
  ::MessageBox(NULL, "Could not load ICMP.DLL", "Error:", MB_OK);
  return;
 }
 // 获得ICMP.DLL中导出函数指针
 pIcmpCreateFile = (HANDLE (WINAPI *)(void))GetProcAddress((HMODULE)hndlIcmp,"IcmpCreateFile");
 pIcmpCloseHandle = (BOOL (WINAPI *)(HANDLE))GetProcAddress((HMODULE)hndlIcmp,"IcmpCloseHandle");
 pIcmpSendEcho = (DWORD (WINAPI *)(HANDLE,DWORD,LPVOID,WORD,PIPINFO,LPVOID,DWORD,DWORD))
GetProcAddress((HMODULE)hndlIcmp,"IcmpSendEcho");
 // 检查所有的指针
 if (pIcmpCreateFile == NULL pIcmpCloseHandle == NULL pIcmpSendEcho == NULL)
 {
  ::MessageBox(NULL, "Error loading ICMP.DLL", "Error:", MB_OK);
  FreeLibrary((HMODULE)hndlIcmp);
  return;
 }

 // 初始化WinSock
 nRet = WSAStartup(0x0101, &wsaData );
 if (nRet)
 {
  ::MessageBox(NULL, "WSAStartup() error:", "Error:", MB_OK);
  WSACleanup();
  FreeLibrary((HMODULE)hndlIcmp);
  return;
 }
 // 检查WinSock的版本
 if (0x0101 != wsaData.wVersion)
 {
  ::MessageBox(NULL, "No WinSock version 1.1 support found", "Error:", MB_OK);
  WSACleanup();
  FreeLibrary((HMODULE)hndlIcmp);
  return;
 }
 bValid = TRUE;
}

  CPing类的析构函数完成相反的动作:


CPing::~CPing()
{
 WSACleanup();
 FreeLibrary((HMODULE)hndlIcmp);
}
  CPing类的Ping函数是最核心的函数,实现真正的ping操作:

int CPing::Ping(char *strHost)
{
 strUCt in_addr iaDest; // Internet地址结构体
 LPHOSTENT pHost; // 主机入口结构体指针
 DWORD *dwAddress; // IP地址
 IPINFO ipInfo; // IP选项结构体
 ICMPECHO icmpEcho; // ICMP Echo回复buffer
 HANDLE hndlFile; // IcmpCreateFile函数打开的句柄

 if (!bValid)
 {
  return FALSE;
 }

 //使用inet_addr()以判定ping目标为地址还是名称
 iaDest.s_addr = inet_addr(strHost);
 if (iaDest.s_addr == INADDR_NONE)
  pHost = gethostbyname(strHost);
 else
  pHost = gethostbyaddr((const char*) &iaDest, sizeof(struct in_addr),AF_INET);
  if (pHost == NULL)
  {
   return FALSE;
  }

  // 拷贝IP地址
  dwAddress = (DWORD*)(*pHost->h_addr_list);

  // 获得ICMP Echo句柄
  hndlFile = pIcmpCreateFile();

  // 设置发送信息缺省值
  ipInfo.Ttl = 255;
  ipInfo.Tos = 0;
  ipInfo.IPFlags = 0;
  ipInfo.OptSize = 0;
  ipInfo.Options = NULL;
  icmpEcho.Status = 0;
  // 请求一个ICMP echo
  pIcmpSendEcho(hndlFile, *dwAddress, NULL, 0, &ipInfo, &icmpEcho, sizeof(struct tagICMPECHO), 1000);

  //设置结果
  iaDest.s_addr = icmpEcho.Source;
  if (icmpEcho.Status)
  {
   return FALSE;
  }

  // 关闭ICMP Echo句柄
  pIcmpCloseHandle(hndlFile);
  return TRUE;
}
  其中所使用的相关结构体定义为:

typedef struct tagIPINFO
{
 u_char Ttl; // TTL
 u_char Tos; // 服务类型
 u_char IPFlags; // IP标志
 u_char OptSize; // 可选数据大小
 u_char *Options; // 可选数据buffer
} IPINFO, *PIPINFO;

typedef struct tagICMPECHO
{
 u_long Source; // 源地址
 u_long Status; // IP状态
 u_long RTTime; // RTT
 u_short DataSize; // 回复数据大小
 u_short Reserved; // 保留
 void *pData; // 回复数据buffer
 IPINFO ipInfo; // 回复IP选项
} ICMPECHO, *PICMPECHO;

使用Raw Socket实现Ping

  仅仅采用ICMP.DLL并不能完全实现ICMP灵活多变的各类报文,只有使用Raw Socket才是ICMP的终极解决之道。

  使用Raw Socket发送ICMP报文前,我们要完全依靠自己的代码组装报文:

//功能:初始化ICMP的报头, 给data部分填充数据, 计算校验和
void init_ping_packet(ICMPHeader *icmp_hdr, int packet_size, int seq_no)
{
 //设置ICMP报头字段
 icmp_hdr->type = ICMP_ECHO_REQUEST;
 icmp_hdr->code = 0;
 icmp_hdr->checksum = 0;
 icmp_hdr->id = (unsigned short)GetCurrentProcessId();
 icmp_hdr->seq = seq_no;
 icmp_hdr->timestamp = GetTickCount();

 // 填充data域
 const unsigned long int deadmeat = 0xDEADBEEF;
 char *datapart = (char*)icmp_hdr + sizeof(ICMPHeader);
 int bytes_left = packet_size - sizeof(ICMPHeader);
 while (bytes_left > 0)
 {
  memcpy(datapart, &deadmeat, min(int(sizeof(deadmeat)), bytes_left));
  bytes_left -= sizeof(deadmeat);
  datapart += sizeof(deadmeat);
 }

 // 计算校验和
 icmp_hdr->checksum = ip_checksum((unsigned short*)icmp_hdr, packet_size);
}

  计算校验和(Checksum)的函数为:

//功能:计算ICMP包的校验和
unsigned short ip_checksum(unsigned short *buffer, int size)
{
 unsigned long cksum = 0;

 // 将所有的16数相加
 while (size > 1)
 {
  cksum += *buffer++;
  size -= sizeof(unsigned short);
 }
 if (size) //加上最后一个BYTE
 {
  cksum += *(unsigned char*)buffer;
 }

 //和的前16位和后16位相加
 cksum = (cksum >> 16) + (cksum &0xffff);
 cksum += (cksum >> 16);

 return (unsigned short)(~cksum);
}
  在真正发送Ping报文前,需要先初始化Raw Socket:

// 功能:初始化RAW Socket, 设置ttl, 初始化目标地址
// 返回值:<0 失败
int setup_for_ping(char *host, int ttl, SOCKET &sd, sockaddr_in &dest)
{
 // 创建原始套接字
 sd = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, 0, 0, 0);
 if (sd == INVALID_SOCKET)
 {
  cerr << "Failed to create raw socket: " << WSAGetLastError() << endl;
  return - 1;
 }

 if (setsockopt(sd, IPPROTO_IP, IP_TTL, (const char*) &ttl, sizeof(ttl)) ==SOCKET_ERROR)
 {
  cerr << "TTL setsockopt failed: " << WSAGetLastError() << endl;
  return - 1;
 }

 // 初始化目标主机信息块
 memset(&dest, 0, sizeof(dest));

 // 将第1个参数转换为目标IP地址
 unsigned int addr = inet_addr(host);
 if (addr != INADDR_NONE)
 {
  // 为IP地址
  dest.sin_addr.s_addr = addr;
  dest.sin_family = AF_INET;
 }
 else
 {
  // 非IP地址,进行主机名和IP地址的转换
  hostent *hp = gethostbyname(host);
  if (hp != 0)
  {
   // 查找主机名对应的IP地址
   memcpy(&(dest.sin_addr), hp->h_addr, hp->h_length);
   dest.sin_family = hp->h_addrtype;
  }
  else
  {
   // 不能识别的主机名
   cerr << "Failed to resolve " << host << endl;
   return - 1;
  }
 }
 return 0;
}
  下面可以利用Raw Socket发送生成的ICMP报文:


//功能:发送生成的ICMP包
//返回值:<0 发送失败
int send_ping(SOCKET sd, const sockaddr_in &dest, ICMPHeader *send_buf, int packet_size)
{
 // 发送send_buf缓冲区中的报文
 cout << "Sending " << packet_size << " bytes to " << inet_ntoa(dest.sin_addr)
 << "..." << flush;
 int bwrote = sendto(sd, (char*)send_buf, packet_size, 0, (sockaddr*) &dest,sizeof(dest));
 if (bwrote == SOCKET_ERROR)
 {
  cerr << "send failed: " << WSAGetLastError() << endl;
  return - 1;
 }
 else if (bwrote < packet_size)
 {
  cout << "sent " << bwrote << " bytes..." << flush;
 }
 return 0;
}
  发送Ping报文后,我们需要接收Ping回复ICMP报文:

//功能:接收Ping回复
//返回值: <0 接收失败
int recv_ping(SOCKET sd, sockaddr_in &source, IPHeader *recv_buf, int packet_size)
{
 // 等待Ping回复
 int fromlen = sizeof(source);
 int bread = recvfrom(sd, (char*)recv_buf, packet_size + sizeof(IPHeader), 0,(sockaddr*) &source, &fromlen);
 if (bread == SOCKET_ERROR)
 {
  cerr << "read failed: ";
  if (WSAGetLastError() == WSAEMSGSIZE)
  {
   cerr << "buffer too small" << endl;
  }
  else
  {
   cerr << "error #" << WSAGetLastError() << endl;
  }
  return - 1;
 }
 return 0;
}
  并使用如下函数对接收到的报文进行解析:

// 功能:解析接收到的ICMP报文
// 返回值: -2忽略, -1失败, 0 成功
int decode_reply(IPHeader *reply, int bytes, sockaddr_in *from)
{
 // 偏移到ICMP报头
 unsigned short header_len = reply->h_len *4;
 ICMPHeader *icmphdr = (ICMPHeader*)((char*)reply + header_len);

 // 报文太短
 if (bytes < header_len + ICMP_MIN)
 {
  cerr << "too few bytes from " << inet_ntoa(from->sin_addr) << endl;
  return - 1;
 }
 // 解析回复报文类型
 else if (icmphdr->type != ICMP_ECHO_REPLY)
 {
  //非正常回复
  if (icmphdr->type != ICMP_TTL_EXPIRE)
  {
   //ttl减为零
   if (icmphdr->type == ICMP_DEST_UNREACH)
   {
    //主机不可达
    cerr << "Destination unreachable" << endl;
   }
   else
   {
    //非法的ICMP包类型
    cerr << "Unknown ICMP packet type " << int(icmphdr->type) <<" received" << endl;
   }
   return - 1;
  }
 }
 else if (icmphdr->id != (unsigned short)GetCurrentProcessId())
 {
  //不是本进程发的包, 可能是同机的其它ping进程发的
  return - 2;
 }

 // 指出往返时间TTL
 int nHops = int(256-reply->ttl);
 if (nHops == 192)
 {
  // TTL came back 64, so ping was probably to a host on the
  // LAN -- call it a single hop.
  nHops = 1;
 }
 else if (nHops == 128)
 {
  // Probably localhost
  nHops = 0;
 }

 // 输出信息
 cout << endl << bytes << " bytes from " << inet_ntoa(from->sin_addr) <<", icmp_seq " << icmphdr->seq << ", ";
 if (icmphdr->type == ICMP_TTL_EXPIRE)
 {
  cout << "TTL expired." << endl;
 }
 else
 {
  cout << nHops << " hop" << (nHops == 1 ? "" : "s");
  cout << ", time: " << (GetTickCount() - icmphdr->timestamp) << " ms." <<endl;
 }
 return 0;
}


  为了在Visual C++中更加方便地使用发送和接收ICMP报文,我们可以使用由Jay Wheeler编写的CIcmp(An ICMP Class For MFC)类,在著名的开发网站的如下地址可以下载。

  这个类的简要框架如下:

class CIcmp: public CSocket
{
 // Attributes
 public:
  BOOL OpenNewSocket(HWND hWnd, unsigned int NotificationMessage, long NotifyEvents);
  BOOL OpenNewSocket(HWND hWnd, unsigned int NotificationMessage, long NotifyEvents, int AFamily, int AType, int AProtocol);
  int CloseIcmpSocket(void);
  BOOL Connect(int ReceiveTimeout, int SendTimeout);
  BOOL Connect(LPINT ReceiveTimeout, LPINT SendTimeout, int AFamily, int AType, int AProtocol);
  int SetTTL(int TTL);
  int SetAsynchNotification(HWND hWnd, unsigned int Message, long Events);
  int Receive(LPSTR pIcmpBuffer, int IcmpBufferSize);
  unsigned long GetIPAddress(LPSTR iHostName);
  int Ping(LPSTR pIcmpBuffer, int IcmpBufferSize);
  unsigned short IcmpChecksum(unsigned short FAR *lpBuf, int Len);
  void DisplayError(CString ErrorType, CString FunctionName);
  // Operations
 public:
  CIcmp(void);
  CIcmp(CIcmp &copy);
  ~CIcmp(void);
 public:
  // I/O Buffer Pointers
  LPIcmpHeader pIcmpHeader;
  LPIpHeader pIpHeader;

  SOCKET icmpSocket;
  SOCKADDR_IN icmpSockAddr;
  SOCKADDR_IN rcvSockAddr;
  DWORD icmpRoundTripTime;
  DWORD icmpPingSentAt;
  DWORD icmpPingReceivedAt;

  int icmpRcvLen;
  int icmpHops;
  int icmpMaxHops;
  int icmpCurSeq;
  int icmpCurId;
  int icmpPingTimer;
  int icmpSocketError;
  int icmpSocketErrorMod;
  unsigned long icmpHostAddress;
 protected:
};
  初始化网络连接的函数:

BOOL CIcmp::Connect(LPINT ReceiveTimeout, LPINT SendTimeout, int AFamily, int AType, int AProtocol)
{
 int Result;
 icmpSocket = NULL;
 icmpSocket = socket(AFamily, AType, AProtocol);

 if (icmpSocket == INVALID_SOCKET)
 {
  icmpSocketError = WSAGetLastError();
  icmpSocketErrorMod = 1;
  return FALSE;
 }

 //
 // Set receive timeout
 //

 Result = setsockopt(icmpSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)ReceiveTimeout, sizeof(int));
 if (Result == SOCKET_ERROR)
 {
  icmpSocketError = WSAGetLastError();
  icmpSocketErrorMod = 2;
  closesocket(icmpSocket);
  icmpSocket = INVALID_SOCKET;
  return FALSE;
 }

 //
 // Set send timeout
 //
 Result = setsockopt(icmpSocket, SOL_SOCKET, SO_SNDTIMEO, (char*)SendTimeout,sizeof(int));
 if (Result == SOCKET_ERROR)
 {
  icmpSocketError = WSAGetLastError();
  icmpSocketErrorMod = 3;
  closesocket(icmpSocket);
  icmpSocket = INVALID_SOCKET;
  return FALSE;
 }
 icmpCurSeq = 0;
 icmpCurId = (USHORT)GetCurrentProcessId();
 icmpHops = 0;

 return TRUE;
}
  接收的函数:


int CIcmp::Receive(LPSTR pIcmpBuffer, int IcmpBufferSize)
{
LPSOCKADDR pRcvSockAddr = (LPSOCKADDR)&rcvSockAddr;

int Result;
int RcvIpHdrLen;

icmpPingReceivedAt = GetTickCount();

icmpCurId = 0;

rcvSockAddr.sin_family = AF_INET;
rcvSockAddr.sin_addr.s_addr = INADDR_ANY;
rcvSockAddr.sin_port = 0;

RcvIpHdrLen = sizeof rcvSockAddr;

Result = recvfrom (icmpSocket,
pIcmpBuffer,
IcmpBufferSize,
0,
pRcvSockAddr,
&RcvIpHdrLen);

if (Result == SOCKET_ERROR)
{
icmpSocketError = WSAGetLastError();
icmpSocketErrorMod = 1;
DisplayError ("Receive","CIcmp::Receive");
return Result;
}

icmpRcvLen = Result;

pIpHeader = (LPIpHeader)pIcmpBuffer;

RcvIpHdrLen = pIpHeader->HeaderLength * 4;
if (Result < RcvIpHdrLen + ICMP_MIN)
{
//
// Too few bytes received
//
MessageBox(NULL,
"Short message!",
"CIcmp::Receive",
MB_OKMB_SYSTEMMODAL);
icmpSocketErrorMod = 2;
return Result;
}

pIcmpHeader = (LPIcmpHeader)(pIcmpBuffer + RcvIpHdrLen);

icmpCurId = pIcmpHeader->IcmpId;
icmpRoundTripTime = icmpPingReceivedAt - pIcmpHeader->IcmpTimestamp;

if (pIcmpHeader->IcmpType != ICMP_ECHOREPLY)
{
//
// Not an echo response!
//
return Result;
}

icmpCurSeq = pIcmpHeader->IcmpSeq;

return Result;
}
  异步通知主窗口:

int CIcmp::SetAsynchNotification(HWND hWnd, unsigned int Message, long Events)
{
 int Result = WSAAsyncSelect (icmpSocket,hWnd, Message, Events);
 if (Result == SOCKET_ERROR)
 {
  icmpSocketError = WSAGetLastError();
  icmpSocketErrorMod = 1;
  icmpSocket = INVALID_SOCKET;
 }
 return Result;
}
  设置TTL:

int CIcmp::SetTTL(int TTL)
{
 int Result;
 Result = setsockopt (icmpSocket, IPPROTO_IP, IP_TTL, (LPSTR)&TTL, sizeof(int));

 if (Result == SOCKET_ERROR)
 {
  icmpSocketErrorMod = 1;
  icmpSocketError = WSAGetLastError();
 }
 return Result;
}
  Ping命令的函数:


int CIcmp::Ping (LPSTR pIcmpBuffer, int DataLen)
{
 int Result;
 int IcmpBufferSize = DataLen + IcmpHeaderLength;
 pIcmpHeader = (LPIcmpHeader)pIcmpBuffer;

 memset (pIcmpBuffer, 'E', IcmpBufferSize);
 memset (pIcmpHeader, 0, IcmpHeaderLength);

 pIcmpHeader->IcmpType = ICMP_ECHO;
 pIcmpHeader->IcmpCode = 0;
 pIcmpHeader->IcmpChecksum = 0;
 pIcmpHeader->IcmpId = icmpCurId;
 pIcmpHeader->IcmpSeq = icmpCurSeq;
 pIcmpHeader->IcmpTimestamp = GetCurrentTime();

 pIcmpHeader->IcmpChecksum = IcmpChecksum ((USHORT FAR *)pIcmpBuffer,IcmpBufferSize);

 icmpPingSentAt = GetCurrentTime();
 Result = sendto (icmpSocket, pIcmpBuffer, IcmpBufferSize, 0, (LPSOCKADDR)&icmpSockAddr, sizeof icmpSockAddr);

 if (Result == SOCKET_ERROR)
 {
  icmpSocketError = WSAGetLastError();
  icmpSocketErrorMod = 1;
 }
 return Result;
}

(出处:网侠)

数据库录入:chqnet    责任编辑:chqnet 
  • 上一个数据库:

  • 下一个数据库:
  • 最新热点 最新推荐 相关数据库
    利用套接字机制实现Flash与数…
    原始套接字透析之实现sniffe…
    黑客之旅――原始套接字透析…
    原始套接字透析之Raw Socket…
    原始套接字透析之ICMP拒绝服…
    原始套接字透析之实现路由欺…
    原始套接字透析之实现包分析
    原始套接字透析之综合实例:…
      网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)