ping的实现(原始套接字系列三)

使用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;
}

ping的实现(原始套接字系列三),布布扣,bubuko.com

时间: 2024-08-04 05:30:42

ping的实现(原始套接字系列三)的相关文章

原始套接字简介(原始套接字系列一)

大多数程序员所接触到的套接字(Socket)为两类: (1)流式套接字(SOCK_STREAM):一种面向连接的Socket,针对于面向连接的TCP服务应用: (2)数据报式套接字(SOCK_DGRAM):一种无连接的Socket,对应于无连接的UDP服务应用. 从用户的角度来看,SOCK_STREAM.SOCK_DGRAM这两类套接字似乎的确涵盖了TCP/IP应用的全部,因为基于TCP/IP的应用,从协议栈的层次上讲,在传输层的确只可能建立于TCP或UDP协议之上(图1),而SOCK_STRE

ICMP拒绝服务攻击(原始套接字系列四)

拒绝服务攻击(DoS)企图通过使被攻击的计算机资源消耗殆尽从而不能再提供服务,拒绝服务攻击是最容易实施的攻击行为.中美黑客大战中的中国黑客一般对美进行的就是拒绝服务攻击,其技术手段大多不够高明. ICMP实现拒绝服务攻击的途径有二:一者"单刀直入",一者"借刀杀人".具体过程分析如下:   ICMPFLOOD攻击 大量的 ICMP消息发送给目标系统,使得它不能够对合法的服务请求做出响应.中美黑客大战中的多数中国黑客采用的正是此项技术.ICMP FLOOD攻击实际上是

原始套接字基础(原始套接字系列二)

在进入Raw Socket多种强大的应用之前,我们先讲解怎样建立一个Raw Socket及怎样用建立的Raw Socket发送和接收IP包. 建立Raw Socket 在Windows平台上,为了使用Raw Socket,需先初始化WINSOCK: // 启动 WinsockWSAData wsaData;if (WSAStartup(MAKEWORD(2, 1), &wsaData) != 0){ cerr << "Failed to find Winsock 2.1 or

ip欺骗(原始套接字系列九)

由于使用Raw Socket的时候,IP报头可完全由程序员自定义,所以我们可以任意地修改本地发送包的IP地址,使得接收方错误的认为IP报文是由欺骗地址发出的. 下面的程序演示了向某目标发送IP地址伪装的UDP报文的过程: void sendPesuoIpUDP(void){ WSADATA wsd; if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) { printf("WSAStartup() failed: %d ", GetLastErr

SNIFF(原始套接字系列六)

大家知道,以太网采用广播机制,所有与网络连接的工作站都可以看到网络上传递的数据.通过查看包含在帧中的目标地址,确定是否进行接收或放弃.如果证明数据确实是发给自己的,工作站将会接收数据并传递给高层协议进行处理.但是,如果让网卡置于混杂模式(Promiscuous mode),则网卡不会鉴别帧的MAC地址,而是一律接收. 上图给出了以太网的帧格式,网卡是通过图中的MAC地址进行ID标识的.传说中的网络嗅探(sniffer)就是指让网卡进入混杂模式从而接收正在局域网总线上发送的所有报文.为什么能够嗅探

ARP欺骗(原始套接字系列八)

ARP欺骗的原理可简单的解释如下:假设有三台主机A,B,C位于同一个交换式局域网中,监听者处于主机A,而主机B,C正在通信.现在A希望能嗅探到B->C的数据,于是A就可以伪装成C对B做ARP欺骗--向B发送伪造的ARP应答包,应答包中IP地址为C的IP地址而MAC地址为A的MAC地址. 这个应答包会刷新B的ARP缓存,让B认为A就是C,说详细点,就是让B认为C的IP地址映射到的MAC地址为主机A的MAC地址.这样,B想要发送给C的数据实际上却发送给了A,就达到了嗅探的目的.我们在嗅探到数据后,还

Raw_Socket原始套接字

对于raw socket,只有root权限才能够创建.raw socket的作用主要有三个方面:1.通过raw socket来接收发向本机的ICMP,IGMP协议包,或者用来发送这些协议包.2.接收发向本机但TCP/IP栈不能够处理的IP包:现在许多操作系统在实现网络部分的时候,通常只实现了常用的几种协议,如tcp,udp,icmp等,但象其它的如ospf,ggp等协议,操作系统往往没有实现,如果自己有必要编写位于其上的应用,就必须借助raw socket来实现,这是因为操作系统遇到自己不能够处

002.原始套接字,构建ICMP包,实现Ping程序,简化版

大致流程: 将ICMP头和时间数据设置好后,通过创建好的原始套接字socket发出去.目的主机计算效验和后会将数据原样返回,用当前时间和返回的数据结算时间差,计算出rtt. 其中: 1.我们以ping www.baidu.com为例,但我们并没有计算平均rtt 2.我们并没有手动创建IP头,而是交给了内核去帮我们处理 3.中间设计到的函数需要查相关资料 代码实现: 1 /* 2 ===========================================================

《网络编程》原始套接字 ---ping程序实现

概述 基于字节流套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)不可以访问传输层协议,只是对应用层的报文进行操作,传输层的数据报格式都是由系统提供的协议栈实现,用户只需要填充相应的应用层报文,由系统完成底层报文首部的填充并发送.原始套接字(SOCK_RAW)可以访问位于基层的传输层协议,原始套接字没有端口号. 原始套接字(SOCK_RAW)是一种不同于 SOCK_STREAM.SOCK_DGRAM 的套接字,它实现于系统核心.原始套接字使进程可以读与写 ICMP.IGMP