黑客之旅――原始套接字(Raw Socket)透析(3)--用Raw Socket实现Ping

简介:
3.用Raw Socket实现Ping
极其常用的Ping命令通过向计算机发送ICMP Echo请求报文并且监听回应报文的返回,以校验与远程计算机或本地计算机的连接。
3.1 使用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;
3.2 使用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)类,在著名的开发网站的如下地址可以下载:
[url]http://www.codeguru.com/cpp/i-n/internet/network/article.php/c3395/[/url]
这个类的简要框架如下:
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_OK|MB_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;
}


 本文转自 21cnbao 51CTO博客,原文链接:http://blog.51cto.com/21cnbao/120089,如需转载请自行联系原作者



相关文章
|
网络协议 Linux
大师的错误?UNP1中raw socket不能接收TCP和UDP的错误
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。 作者:gfree.wind@gmail.com 博客:linuxfocus.blog.chinaunix.net      在UNP1中的28.4 Raw Socket Input中,大师是这样说的。
1376 0
|
2月前
|
网络协议 测试技术 网络安全
Python编程-Socket网络编程
Python编程-Socket网络编程
31 0
|
5月前
|
网络协议 开发者 Python
深度探索Python Socket编程:从理论到实践,进阶篇带你领略网络编程的魅力!
【7月更文挑战第25天】在网络编程中, Python Socket编程因灵活性强而广受青睐。本文采用问答形式深入探讨其进阶技巧。**问题一**: Socket编程基于TCP/IP,通过创建Socket对象实现通信,支持客户端和服务器间的数据交换。**问题二**: 提升并发处理能力的方法包括多线程(适用于I/O密集型任务)、多进程(绕过GIL限制)和异步IO(asyncio)。**问题三**: 提供了一个使用asyncio库实现的异步Socket服务器示例,展示如何接收及响应客户端消息。通过这些内容,希望能激发读者对网络编程的兴趣并引导进一步探索。
62 4
|
5月前
|
开发者 Python
Python Socket编程:不只是基础,更有进阶秘籍,让你的网络应用飞起来!
【7月更文挑战第25天】在网络应用蓬勃发展的数字时代,Python凭借其简洁的语法和强大的库支持成为开发高效应用的首选。本文通过实时聊天室案例,介绍了Python Socket编程的基础与进阶技巧,包括服务器与客户端的建立、数据交换等基础篇内容,以及使用多线程和异步IO提升性能的进阶篇。基础示例展示了服务器端监听连接请求、接收转发消息,客户端连接服务器并收发消息的过程。进阶部分讨论了如何利用Python的`threading`模块和`asyncio`库来处理多客户端连接,提高应用的并发处理能力和响应速度。掌握这些技能,能使开发者在网络编程领域更加游刃有余,构建出高性能的应用程序。
38 3
|
5月前
|
网络协议 Python
网络世界的建筑师:Python Socket编程基础与进阶,构建你的网络帝国!
【7月更文挑战第26天】在网络的数字宇宙中,Python Socket编程是开启网络世界大门的钥匙。本指南将引领你从基础到实战,成为网络世界的建筑师。
67 2