透析ICMP协议(4): 应用篇ping(RAW Socket)

2016-02-19 17:13 4 1 收藏

下面是个超简单的透析ICMP协议(4): 应用篇ping(RAW Socket)教程,图老师小编精心挑选推荐,大家行行好,多给几个赞吧,小编吐血跪求~

【 tulaoshi.com - 编程语言 】

  原理简介:

  --------

  用RAW Socket实现的ping可能比上一节的应用ICMP.DLL的程序庞大些, 但是这才是我们需要关注的东西, 我的观点真正想做网络开发的程序员应该静下心来读读这篇文章, 相信你会从中获益颇多. 中间我也会讲解一些东西为后一章的路由追踪做一些铺垫.

  另一个重要的要讲的东西, 微软宣布随时不支持上节讲的ping用到的开发接口, 但是本节的讲的是更一般的东西. 所以它不会过时, 甚至做很小的改动就可以移植到别的系统上去. 系统移植不是我们的讲的重点. 但是微软的长期支持足以引起我们充分的重视.

  如何少作变动来使的这个程序实现追踪路由的功能, 这里只是抛砖引玉. 将ICMP包中IP包的包头该为特定的值就能得到那个路由器的IP(要求到达目的地的跳数大于你设的特定值).

  这个程序需要windows2k/WindowsXP/WindowsNT平台和系统管理员的权限.

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com/bianchengyuyan/)

  具体实现:

  --------

  这段源代码大部分来自:

  http://tangentsoft.net/wskfaq/examples/rawping.html

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com/bianchengyuyan/)

  [bugfree]只做了少量修改,给出了大量的注释, 最后结合经验给出了自己的建议.

  ----------

  

/** 程序名: rawping_driver.cpp* 说明:* 驱动程序,也是主函数*/#include winsock2.h#include iostream.h#include "rawping.h"#define DEFAULT_PACKET_SIZE 32 // 默认ICMP包字节数#define DEFAULT_TTL 30 // 默认TTL值#define MAX_PING_DATA_SIZE 1024 // 最大数据块#define MAX_PING_PACKET_SIZE (MAX_PING_DATA_SIZE + sizeof(IPHeader)) //最大ICMP包长度/* 为 send_buf 和 recv_buf 分配内存* send_buf大小为 packet_size* recv_buf大小为 MAX_PING_PACKET_SIZE, 保证大于send_buf*/int allocate_buffers(ICMPHeader*& send_buf, IPHeader*& recv_buf,int packet_size); ///////////////////////////////////////////////////////////////////////// Program entry pointint main(int argc, char* argv[]){int seq_no = 0; //用在发送和接受的ICMP包头中ICMPHeader* send_buf = 0;IPHeader* recv_buf = 0;// 判断命令行是否合法if (argc  2) {cerr  "usage: "  argv[0]  " host [data_size] [ttl]" endl;cerr  "tdata_size can be up to "  MAX_PING_DATA_SIZE " bytes. Default is "  DEFAULT_PACKET_SIZE  "." endl;cerr  "tttl should be 255 or lower. Default is " DEFAULT_TTL  "."  endl;return 1;}// 处理命令行参数int packet_size = DEFAULT_PACKET_SIZE;int ttl = DEFAULT_TTL;if (argc  2) {int temp = atoi(argv[2]);if (temp != 0) {packet_size = temp;}if (argc  3) {temp = atoi(argv[3]);if ((temp = 0) && (temp = 255)) {ttl = temp;}}}packet_size = max(sizeof(ICMPHeader),min(MAX_PING_DATA_SIZE, (unsigned int)packet_size));// 启动 WinsockWSAData wsaData;if (WSAStartup(MAKEWORD(2, 1), &wsaData) != 0) {cerr  "Failed to find Winsock 2.1 or better."  endl;return 1;}SOCKET sd; // RAW Socket句柄sockaddr_in dest, source;// 三个任务(创建sd, 设置ttl, 初试dest的值)if (setup_for_ping(argv[1], ttl, sd, dest)  0) {goto cleanup; //释放资源并退出}// 为send_buf和recv_buf分配内存if (allocate_buffers(send_buf, recv_buf, packet_size)  0) {goto cleanup;}// 初试化IMCP数据包(type=8,code=0)init_ping_packet(send_buf, packet_size, seq_no);// 发送ICMP数据包if (send_ping(sd, dest, send_buf, packet_size) = 0) {while (1) {// 接受回应包if (recv_ping(sd, source, recv_buf, MAX_PING_PACKET_SIZE) 0) {// Pull the sequence number out of the ICMP header. If// it's bad, we just complain, but otherwise we take// off, because the read failed for some reason.unsigned short header_len = recv_buf-h_len * 4;ICMPHeader* icmphdr = (ICMPHeader*)((char*)recv_buf + header_len);if (icmphdr-seq != seq_no) {cerr  "bad sequence number!"  endl;continue;}else {break;}}if (decode_reply(recv_buf, packet_size, &source) != -2) {// Success or fatal error (as opposed to a minor error)// so take off.break;}}}cleanup:delete[]send_buf; //释放分配的内存delete[]recv_buf;WSACleanup(); // 清理winsockreturn 0;}// 为send_buf 和 recv_buf的内存分配. 太简单, 我略过int allocate_buffers(ICMPHeader*& send_buf, IPHeader*& recv_buf,int packet_size){// First the send buffersend_buf = (ICMPHeader*)new char[packet_size];if (send_buf == 0) {cerr  "Failed to allocate output buffer."  endl;return -1;}// And then the receive bufferrecv_buf = (IPHeader*)new char[MAX_PING_PACKET_SIZE];if (recv_buf == 0) {cerr  "Failed to allocate output buffer."  endl;return -1;}return 0;}/** 程序名: rawping.h* 说明:* 主要函数库头文件*/#define WIN32_LEAN_AND_MEAN#include winsock2.h// ICMP 包类型, 具体参见本文的第一节#define ICMP_ECHO_REPLY 0#define ICMP_DEST_UNREACH 3#define ICMP_TTL_EXPIRE 11#define ICMP_ECHO_REQUEST 8// 最小的ICMP包大小#define ICMP_MIN 8// IP 包头struct IPHeader {BYTE h_len:4; // Length of the header in dwordsBYTE version:4; // Version of IPBYTE tos; // Type of serviceUSHORT total_len; // Length of the packet in dwordsUSHORT ident; // unique identifierUSHORT flags; // FlagsBYTE ttl; // Time to live, 这个字段我在下一节中用来实现Tracert功能BYTE proto; // Protocol number (TCP, UDP etc)USHORT checksum; // IP checksumULONG source_ip;ULONG dest_ip;};// ICMP 包头(实际的包不包括timestamp字段,// 作者用来计算包的回应时间,其实完全没有必要这样做)struct ICMPHeader {BYTE type; // ICMP packet typeBYTE code; // Type sub codeUSHORT checksum;USHORT id;USHORT seq;ULONG timestamp; // not part of ICMP, but we need it};extern USHORT ip_checksum(USHORT* buffer, int size);extern int setup_for_ping(char* host, int ttl, SOCKET& sd, sockaddr_in& dest);extern int send_ping(SOCKET sd, const sockaddr_in& dest, ICMPHeader* send_buf, int packet_size);extern int recv_ping(SOCKET sd, sockaddr_in& source, IPHeader* recv_buf,int packet_size);extern int decode_reply(IPHeader* reply, int bytes, sockaddr_in* from);extern void init_ping_packet(ICMPHeader* icmp_hdr, int packet_size, int seq_no);

  

/** 程序名: rawping.cpp* 说明:* 主要函数库实现部分*/include winsock2.h#include ws2tcpip.h#include iostream.h#include "rawping.h"// 计算ICMP包的校验和的简单算法, 很多地方都有说明, 这里没有必要详细将// 只是一点要提, 做校验之前, 务必将ICMP包头的checksum字段置为0USHORT ip_checksum(USHORT* buffer, int size){unsigned long cksum = 0;// Sum all the words together, adding the final byte if size is oddwhile (size  1) {cksum += *buffer++;size -= sizeof(USHORT);}if (size) {cksum += *(UCHAR*)buffer;}// Do a little shufflingcksum = (cksum  16) + (cksum & 0xffff);cksum += (cksum  16);// Return the bitwise complement of the resulting mishmashreturn (USHORT)(~cksum);}//初试化RAW Socket, 设置ttl, 初试化dest// 返回值 0 表失败int setup_for_ping(char* host, int ttl, SOCKET& sd, sockaddr_in& dest){// Create the socketsd = 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;}// Initialize the destination host info blockmemset(&dest, 0, sizeof(dest));// Turn first passed parameter into an IP address to pingunsigned int addr = inet_addr(host);if (addr != INADDR_NONE) {// It was a dotted quad number, so save resultdest.sin_addr.s_addr = addr;dest.sin_family = AF_INET;}else {// Not in dotted quad form, so try and look it uphostent* hp = gethostbyname(host);if (hp != 0) {// Found an address for that host, so save itmemcpy(&(dest.sin_addr), hp-h_addr, hp-h_length);dest.sin_family = hp-h_addrtype;}else {// Not a recognized hostname either!cerr  "Failed to resolve "  host  endl;return -1;}}return 0;}//初试化ICMP的包头, 给data部分填充数据, 最后计算整个包的校验和void init_ping_packet(ICMPHeader* icmp_hdr, int packet_size, int seq_no){// Set up the packet's fieldsicmp_hdr-type = ICMP_ECHO_REQUEST;icmp_hdr-code = 0;icmp_hdr-checksum = 0;icmp_hdr-id = (USHORT)GetCurrentProcessId();icmp_hdr-seq = seq_no;icmp_hdr-timestamp = GetTickCount();// "You're dead meat now, packet!"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);}// Calculate a checksum on the resulticmp_hdr-checksum = ip_checksum((USHORT*)icmp_hdr, packet_size);}// 发送生成的ICMP包// 返回值 0 表失败int send_ping(SOCKET sd, const sockaddr_in& dest, ICMPHeader* send_buf,int packet_size){// Send the ping packet in send_buf as-iscout  "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;}// 接受ICMP包// 返回值 0 表失败int recv_ping(SOCKET sd, sockaddr_in& source, IPHeader* recv_buf,int packet_size){// Wait for the ping replyint 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){// 跳过IP包头, 找到ICMP的包头unsigned short header_len = reply-h_len * 4;ICMPHeader* icmphdr = (ICMPHeader*)((char*)reply + header_len);// 包的长度合法, header_len + ICMP_MIN为最小ICMP包的长度if (bytes  header_len + ICMP_MIN) {cerr  "too few bytes from "  inet_ntoa(from-sin_addr) endl;return -1;}// 下面的包类型详细参见我的第一部分 "透析ICMP协议(一): 协议原理"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 != (USHORT)GetCurrentProcessId()) {//不是本进程发的包, 可能是同机的其它ping进程发的return -2;}// 指出包传递了多远// [bugfree]我认为作者这里有问题, 因为有些系统的ttl初值为128如winXP,// 有些为256如我的DNS服务器211.97.168.129, 作者假设为256有点武断,// 可以一起探讨这个问题, 回email:zhangliangsd@hotmail.comint 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 localhostnHops = 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;}

  总结和建议:

  -----------

  bugfree建议其中的这些方面需要改进:

  1. 头文件iostream.h 改为 iostream, 后者是标准C++的头文件

  同时添加对std::cout 和 std::endl;的引用

  对于cerr 建议都改为std::cout(因为后者头文件不支持)

  2. 程序的发送和接受采用了同步的方式, 这使得如果出现网络问题recv_ping将陷入持续等待.

  这是我们不想看到的.

  这三种技术可以达到目的:

  - 使用多线程, 将ping封装进线程, 在主程序中对它的超时进行处理

  - 使用select()函数来实现

  - 使用windows的 WSAAsyncSelect()

  这里对这些方法不作具体讨论, 留给读者自已完成.

来源:https://www.tulaoshi.com/n/20160219/1614605.html

延伸阅读
功能说明: 检测主机。 语 法: ping [-dfnqrRv][-c<完成次数][-i<间隔秒数][-I<网络界面][-l<前置载入][-p<范本样式][-s<数据包大小][-t<存活数值][主机名称或IP地址] 补充说明: 执行ping指令会使用ICMP传输协议,发出要求回应的信息,若远端主机的网络功能没有问题,就会回应该信息,因而得知该主机运作正...
标签: PHP
PHP还给你提供了一种处理多种可能的方式-"if-elseif-else"结构。一个典型的"if-elseif-else"结构语句将如下所示:   -------------------------------------------------------------------------------- if (第一个条件正确) { do this! } elseif (第二个条件正确) { do this! } elseif (...
标签: PHP
表单是一种给你的站点增加交互功能的最快,最容易的途径。表单允许你询问你的顾客是否喜欢你的产品,让不经意访问到你的站点的访问者留下评论,或者向漂亮的美眉们要她们的电话号码。PHP能大大简化基于网页表单提交的数据处理工作-如下面我们的第一个例子所示:   --------------------------------------------------...
标签: PHP
  作者:孙运动 你可能已经注意到,到目前为止,在我们给你的所有例子中,我们都是给你两个页面-一个单纯的具有表单的HTML页面,和另一个用来处理表单输入并产生相应输出的PHP脚本。然而,PHP提供了一种把那两个页面通过 $submit 变量结合在一起的文雅的方法 你已经知道,一旦一个表单提交给PHP脚本,所有的表单变量就变成了的PHP...
标签: PHP
  作者:孙运动 “ ===” 操作符 ------------------------ 我们上面已经提到过, PHP4 增加了一个新的 === 操作符来测试是否变量具有相同的类型。这儿有一个例子: -------------------------------------------------------------------------------- < ? if (!$submit) { // 如果$submit不存在, 这暗示表单还没有...

经验教程

248

收藏

19
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部