如果觉得本文如果帮到你或者你想转载都可以,只需要标注出处即可。谢谢
利用ICMP数据包、C语言实现Ping命令程序,能实现基本的Ping操作,发送ICMP回显请求报文,用于测试—个主机到只一个主机之间的连通情况。通过本程序的训练,熟悉ICMP报文结构,对ICMP有更深的理解,掌握Ping程序的设计方法,掌握网络编程的方法和技巧,从而编写出功能更强大的程序。有关traceroute如果有时间我会也写一篇来进行讲解.W
windows和Linux实现ping的底层思想一样的,代码有细微的差别。如文文件不一样,参数定义不一样等。所以我们要实现ping功能的时候我们需要注意是在Windows上实现还是Linux上实现。
如果你不想看关于ping命令实现的原理,则可以直接通过以下目录跳转到‘8.实现Ping功能’即可.
本文目录
1.ICMP简介
2.ICMP工作原理
3.ICMP报文格式
4.ICMPv4类型
4.1响应请求/应答(ping)
4.2.目标不可到达、源抑制和超时报文
5.ICMP应用
6.ICMP攻击与防御方法
7.IP报文头和ICMP的联系
8.实现Ping功能
8.1.ping实现步骤
8.2.结果及心得
8.3.完整代码
1.ICMP简介
ICMP(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议簇的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。
ICMP协议是一种面向无连接的协议,用于传输出错报告控制信息。它是一个非常重要的协议,它对于网络安全具有极其重要的意义。
ICMP报文通常是由IP层本身、上层的传输协议(TCP或UDP)甚至某些情况下用户应用除法执行的。
ICMP报文是在IP数据报内被封装传输的。
ICMP分为两大类:有关IP数据报传递的ICMP报文(称为差错报文(error message)),以及有关信息采集和配置的ICMP报文(称为查询(query)或者信息类报文(informational message))。
注:ICMP并不为IP网络提供可靠性。相反,它表明了某些类别的故障和配置信息。
2.ICMP工作原理
ICMP提供一致易懂的出错报告信息。发送的出错报文返回到发送原数据的设备,因为只有发送设备才是出错报文的逻辑接受者。发送设备随后可根据ICMP报文确定发生错误的类型,并确定如何才能更好地重发失败的数据包。但是ICMP唯一的功能是报告问题而不是纠正错误,纠正错误的任务由发送方完成。
我们在网络中经常会使用到ICMP协议,比如我们经常使用的用于检查网络通不通的Ping命令(Linux和Windows中均有),这个“Ping”的过程实际上就是ICMP协议工作的过程。还有其他的网络命令如跟踪路由的Tracert命令也是基于ICMP协议的。
3.ICMP报文格式
ICMP报文包含在IP数据报中,属于IP的一个用户,IP头部就在ICMP报文的前面,所以一个ICMP报文包括IP头部、ICMP头部和ICMP报文,IP头部的Protocol值为1就说明这是一个ICMP报文,ICMP头部中的类型(Type)域用于说明ICMP报文的作用及格式,此外还有一个代码(Code)域用于详细说明某种ICMP报文的类型,所有数据都在ICMP头部后面。
ICMPICMP报文格式具体由[RFC777],[RFC792]规范。792是1981年9月更新,而777是1981年4月更新的。目前最新的ICMP报文格式RFC是2007年4月更新的[RFC488].
4.ICMPv4类型
已经定义的ICMP消息类型大约有10多种,每种ICMP数据类型都被封装在一个IP数据包中。主要的ICMP消息类型包括以下几种。
对于ICMPv4,信息类报文包括回显请求和回显应答(分别为类型8和0),以及路由器通告和路由器请求(分别为类型9和10,统一被称为路由器发现)。最常见的差错报文类型包括目的不可达(类型3)、重定向(类型5)、超时(类型11)和参数问题(类型12).下图为一些类型.更多的信息建议去RFC官方查看,Type和Code在IPv4和IPc6不尽相同,所以其中的差异需要我们自行去查看,本图为IPv4版本的,IPv6需要我们自己RFC查找。
1).响应请求/应答(ping)(ICMPv4类型为0/8,ICMPv6类型129/18)
我们日常使用最多的ping,就是响应请求(Type=8)和应答(Type=0),一台主机向一个节点发送一个Type=8的ICMP报文,如果途中没有异常(例如被路由器丢弃、目标不回应ICMP或传输失败),则目标返回Type=0的ICMP报文,说明这台主机存在,更详细的tracert通过计算ICMP报文通过的节点来确定主机与目标之间的网络距离。更多的信息我们可以通过RFC文档了解
2).目标不可到达(ICMPv4类型3,ICMPv6类型1)、源抑制和超时报文(ICMPv4类型11,ICMPv6类型4)
这三种报文的格式是一样的,目标不可到达报文(Type=3)在路由器或主机不能传递数据报时使用,例如我们要连接对方一个不存在的系统端口(端口号小于1024)时,将返回Type=3、Code=3的ICMP报文,它要告诉我们:“嘿,别连接了,我不在家的!”,常见的不可到达类型还有网络不可到达(Code=0)、主机不可到达(Code=1)、协议不可到达(Code=2)等。源抑制则充当一个控制流量的角色,它通知主机减少数据报流量,由于ICMP没有恢复传输的报文,所以只要停止该报文,主机就会逐渐恢复传输速率。最后,无连接方式网络的问题就是数据报会丢失,或者长时间在网络游荡而找不到目标,或者拥塞导致主机在规定时间内无法重组数据报分段,这时就要触发ICMP超时报文的产生。超时报文的代码域有两种取值:Code=0表示传输超时,Code=1表示重组分段超时。更多的信息我们可以通过RFC文档了解
5.ICMP应用
1).ping 命令使用 ICMP 回送请求和应答报文在网络可达性测试中使用的分组网间探测命令 ping 能产生 ICMP 回送请求和应答报文。目的主机收到 ICMP 回送请求报文后立刻回送应答报文,若源主机能收到 ICMP 回送应答报文,则说明到达该主机的网络正常。
2).路由分析诊断程序 tracert 使用了 ICMP时间超过报文tracert 命令主要用来显示数据包到达目的主机所经过的路径。通过执行一个 tracert 到对方主机的命令,返回数据包到达目的主机所经历的路径详细信息,并显示每个路径所消耗的时间。
6.ICMP攻击
涉及ICMP的攻击主要分为3类:泛洪(flood)、炸弹(bomb)、信息泄露(information disclosure).针对TCP的ICMP攻击已经被专门记录在RFC文档中[RFC5927]
1).泛洪(flood)
泛洪将会生成大量流量,导致针对一台或者多台计算机的有效Dos攻击
2).炸弹(bomb)
炸弹类型有时也称为核弹(nuke)类型,指的是发送经过特殊构造的报文,能够导致IP或ICMP的处理崩溃或者终止。
3).信息泄露(information disclosure)
信息泄露攻击本身不会造成危害,但是能够帮助其他攻击方法避免浪费时间或者被发现了。
7.IP报文头和ICMP的联系
ICMP报文是封装在IP数据报的数据部分中进行传输的.
ICMP依靠IP来完成它的任务,它是IP的主要部分。它与传输协议(如TCP和UDP)显著不同:它一般不用于在两点间传输数据。它通常不由网络程序直接使用,除了 ping 和 traceroute 这两个特别的例子。 IPv4中的ICMP被称作ICMPv4,IPv6中的ICMP则被称作ICMPv6。
总的来说,ICMP是封装在IP数据报中进行传输的.具体更多的联系我们通过以下改文章进行详解,从Wireshark抓包然后分析数据包进行两者的区别和联系.
参考文档:https://www.cnblogs.com/CSAH/p/13170860.html
8.实现Ping功能
首先我们注意,本文只是实现ping的最简单的功能即响应请求/应答(ping),故只能够ping IP地址,不能够ping 域名,因为域名到IP地址我们需要经过DNS解析,本文不实现该功能.关于DNS转换到IP地址的详情,有时间有机会我会补上的.
本程序使用的环境是win10+vc++6.0,如果没有安装VC++6.0的或者在Win10安装了无法使用的请查看'Win10安装vc6.0教程'。
该ping功能实现参考了TCP/IP详解 卷1 和 卷2。
1).实现步骤
首先,我们需要先定义初始化一些全局变量,接着我们对需要用到的数据类型结构进行声明定义,我们包含的数据类型结构有IP报头结构、ICMP数据类型结构、结果集类型结构等;对需要使用到的函数进行头文件的导入,主要的区别在于使用的是Windows系统还是Linux系统,导入的头文件也不尽相同。准备工作全都完成了,然后我们就可以定义main函数进行试验的验证测试。
其次,我们需要对每一步的遇到的问题需要写一份说明报告书,以防下次再进行实验时遇到同样的问题时,我们无需再去查找大量资料。
最后,我们对整个实验的总结,对每一步。每一个函数进行详讲.做好注释.
Ping()函数是本程序的核心部分,它基本是调用其他模块的函数来实现最终功能,其主要布骤包括:定义及初始化各个全局变量、打开socket动态库、设置接收和发送超时值、域名地址解析、分配内存、创建及初始化ICMP报文、发送ICMP请求报文、接收ICMP 应答报文以及解读应答报文和输出Ping结果。
注意:创建套接字的时候参数的以及在创建套接字之前必须首先使用WSAStartup函数。
(1)输入时不能输入目标主机名,不然ping结果为TIMEOUT
(2)该模块并非只有处理还包括判断及输出判断结果的含义
(3)程序没运行一次就只能输出四行结果(前提是输入的地址有效),欲再次PING其他地址接着输入下一个ip地址即可
2).代码实现
如果要想实现Windows下ping功能的实现,我们只需要从(1)到(8)复制到任意一个新创建filename.cpp文件中即可执行.或者最简单的方法就是到本文中最低直接复制'完整代码'到任意一个新创建filename.cpp文件中即可执行
(1).头文件、全局变量
#include<stdio.h> #include<Winsock2.h> #include<ws2tcpip.h> #include<stdlib.h> #include<malloc.h> #include<string.h> #pragma comment(lib , "Ws2_32.lib") #define ICMP_ECHO_REQUEST 8 //定义回显请求类型 #define DEF_ICMP_DATA_SIZE 20 //定义发送数据长度 #define DEF_ICMP_PACK_SIZE 32 //定义数据包长度 #define MAX_ICMP_PACKET_SIZE 1024 //定义最大数据包长度 #define DEF_ICMP_TIMEOUT 3000 //定义超时为3秒 #define ICMP_TIMEOUT 11 //ICMP超时报文 #define ICMP_ECHO_REPLY 0 //定义回显应答类型
(2).IP报头据类型
/* *IP报头结构 */ typedef struct { byte h_len_ver ; //IP版本号 byte tos ; // 服务类型 unsigned short total_len ; //IP包总长度 unsigned short ident ; // 标识 unsigned short frag_and_flags ; //标志位 byte ttl ; //生存时间 byte proto ; //协议 unsigned short cksum ; //IP首部校验和 unsigned long sourceIP ; //源IP地址 unsigned long destIP ; //目的IP地址 } IP_HEADER ;
(3).ICMP数据类型
/* *定义ICMP数据类型 */ typedef struct _ICMP_HEADER { byte type ; //类型-----8 byte code ; //代码-----8 unsigned short cksum ; //校验和------16 unsigned short id ; //标识符-------16 unsigned short seq ; //序列号------16 unsigned int choose ; //选项-------32 } ICMP_HEADER ;
(4).ping返回结果集数据类型
typedef struct { int usSeqNo ; //记录序列号 DWORD dwRoundTripTime ; //记录当前时间 byte ttl ; //生存时间 in_addr dwIPaddr ; //源IP地址 } DECODE_RESULT ;
(5).网际校验和
/* *产生网际校验和 */ unsigned short GenerateChecksum(unsigned short *pBuf , int iSize) { unsigned long cksum = 0 ; //开始时将网际校验和初始化为0 while(iSize > 1) { cksum += *pBuf++ ; //将待校验的数据每16位逐位相加保存在cksum中 iSize -= sizeof(unsigned short) ; //每16位加完则将带校验数据量减去16 } //如果待校验的数据为奇数,则循环完之后需将最后一个字节的内容与之前结果相加 if(iSize) { cksum += *(unsigned char*)pBuf ; } //之前的结果产生了进位,需要把进位也加入最后的结果中 cksum = (cksum >> 16) + (cksum & 0xffff) ; cksum += (cksum >> 16) ; return (unsigned short)(~ cksum) ; }
(6).ping信息解析
/* *对ping应答信息进行解析 */ boolean DecodeIcmpResponse_Ping(char *pBuf , int iPacketSize , DECODE_RESULT *stDecodeResult) { IP_HEADER *pIpHrd = (IP_HEADER*)pBuf ; int iIphedLen = 20 ; if(iPacketSize < (int)(iIphedLen + sizeof(ICMP_HEADER))) { printf("size error! ") ; return 0 ; } //指针指向ICMP报文的首地址 ICMP_HEADER *pIcmpHrd = (ICMP_HEADER*)(pBuf + iIphedLen) ; unsigned short usID , usSeqNo ; //获得的数据包的type字段为ICMP_ECHO_REPLY,即收到一个回显应答ICMP报文 if(pIcmpHrd->type == ICMP_ECHO_REPLY) { usID = pIcmpHrd->id ; //接收到的是网络字节顺序的seq字段信息 , 需转化为主机字节顺序 usSeqNo = ntohs(pIcmpHrd->seq) ; } if(usID != GetCurrentProcessId() || usSeqNo != stDecodeResult->usSeqNo) { printf("usID error! ") ; return 0 ; } //记录对方主机的IP地址以及计算往返的时延RTT if(pIcmpHrd->type == ICMP_ECHO_REPLY) { stDecodeResult->dwIPaddr.s_addr = pIpHrd->sourceIP ; stDecodeResult->ttl = pIpHrd->ttl ; stDecodeResult->dwRoundTripTime = GetTickCount() - stDecodeResult->dwRoundTripTime ; return 1 ; } return 0 ; }
(7).ping功能实现集成
void Ping(char *IP) { unsigned long ulDestIP = inet_addr(IP) ; //将IP地址转化为长整形 if(ulDestIP == INADDR_NONE) { //转化不成功时按域名解析 HOSTENT *pHostent = gethostbyname(IP) ; if(pHostent) { ulDestIP = (*(IN_ADDR*)pHostent->h_addr).s_addr ; //将HOSTENT转化为长整形 } else { printf("TIMEOUT ") ; return ; } } //填充目的Socket地址 SOCKADDR_IN destSockAddr ; //定义目的地址 ZeroMemory(&destSockAddr , sizeof(SOCKADDR_IN)) ; //将目的地址清空 destSockAddr.sin_family = AF_INET ; destSockAddr.sin_addr.s_addr = ulDestIP ; destSockAddr.sin_port = htons(0); //初始化WinSock WORD wVersionRequested = MAKEWORD(2,2); WSADATA wsaData; if(WSAStartup(wVersionRequested,&wsaData) != 0) { printf("初始化WinSock失败! ") ; return ; } //使用ICMP协议创建Raw Socket SOCKET sockRaw = WSASocket(AF_INET , SOCK_RAW , IPPROTO_ICMP , NULL , 0 , WSA_FLAG_OVERLAPPED) ; if(sockRaw == INVALID_SOCKET) { printf("创建Socket失败 ! ") ; return ; } //设置端口属性 int iTimeout = DEF_ICMP_TIMEOUT ; if(setsockopt(sockRaw , SOL_SOCKET , SO_RCVTIMEO , (char*)&iTimeout , sizeof(iTimeout)) == SOCKET_ERROR) { printf("设置参数失败! ") ; return ; } if(setsockopt(sockRaw , SOL_SOCKET , SO_SNDTIMEO , (char*)&iTimeout , sizeof(iTimeout)) == SOCKET_ERROR) { printf("设置参数失败! ") ; return ; } //定义发送的数据段 char IcmpSendBuf[DEF_ICMP_PACK_SIZE] ; //填充ICMP数据包个各字段 ICMP_HEADER *pIcmpHeader = (ICMP_HEADER*)IcmpSendBuf; pIcmpHeader->type = ICMP_ECHO_REQUEST ; pIcmpHeader->code = 0 ; pIcmpHeader->id = (unsigned short)GetCurrentProcessId() ; memset(IcmpSendBuf + sizeof(ICMP_HEADER) , 'E' , DEF_ICMP_DATA_SIZE) ; //循环发送四个请求回显icmp数据包 int usSeqNo = 0 ; DECODE_RESULT stDecodeResult ; while(usSeqNo <= 3) { pIcmpHeader->seq = htons(usSeqNo) ; pIcmpHeader->cksum = 0 ; pIcmpHeader->cksum = GenerateChecksum((unsigned short*)IcmpSendBuf , DEF_ICMP_PACK_SIZE) ; //生成校验位 //记录序列号和当前时间 stDecodeResult.usSeqNo = usSeqNo ; stDecodeResult.dwRoundTripTime = GetTickCount() ; //发送ICMP的EchoRequest数据包 if(sendto(sockRaw , IcmpSendBuf , DEF_ICMP_PACK_SIZE , 0 , (SOCKADDR*)&destSockAddr , sizeof(destSockAddr)) == SOCKET_ERROR) { //如果目的主机不可达则直接退出 if(WSAGetLastError() == WSAEHOSTUNREACH) { printf("目的主机不可达! ") ; exit(0) ; } } SOCKADDR_IN from ; int iFromLen = sizeof(from) ; int iReadLen ; //定义接收的数据包 char IcmpRecvBuf[MAX_ICMP_PACKET_SIZE] ; while(1) { iReadLen = recvfrom(sockRaw , IcmpRecvBuf , MAX_ICMP_PACKET_SIZE , 0 , (SOCKADDR*)&from , &iFromLen) ; if(iReadLen != SOCKET_ERROR) { if(DecodeIcmpResponse_Ping(IcmpRecvBuf , sizeof(IcmpRecvBuf) , &stDecodeResult)) { printf("来自 %s 的回复: 字节 = %d 时间 = %dms TTL = %d " , inet_ntoa(stDecodeResult.dwIPaddr) , iReadLen - 20,stDecodeResult.dwRoundTripTime ,stDecodeResult.ttl) ; } break ; } else if(WSAGetLastError() == WSAETIMEDOUT) { printf("time out ! ***** ") ; break ; } else { printf("发生未知错误! ") ; break ; } } usSeqNo++ ; } //输出屏幕信息 printf("Ping complete... ") ; closesocket(sockRaw) ; WSACleanup() ; }
①.inet_addr:可以转化字符串,主要用来将一个十进制的数转化为二进制的数,用途多于ipv4的IP转化。
②.if(IpAddress == INADDR_NONE):INADDR_NONE 是个宏定义,代表IpAddress是否为无效的IP地址。
③.ckaddr_in:定义目的地址信息;
④.ZeroMemory:用0来填充一块内存区域.ZeroMemory只能用于windows平台.
⑤.WSASocket:创建一个原始套接字。使用时需要包含winsock2.h 头文件和链接ws2_32.lib库。
⑥.SOCKET socket==INVALID_SOCKET:如果socket为无效套接字,则结果为true;
⑦.DEF_ICMP_TIMEOUT:报文超时时间.
⑧.setsockopt:选项影响套接口的操作,诸如加急数据是否在普通数据流中接收,广播数据是否可以从套接口发送等等。
⑨.while(usSeqNo <= 3){}:该部分就是实验要求我们一次测试的进行发包4次
(8).Test测试
int main(int argc , char* argv[]) { char com[10] , IP[20] ; while(1){ printf("command>>") ; scanf("%s %s" , com , IP) ; if(strcmp(com , "ping") == 0) { Ping(IP) ; } else { printf("输入错误 ! ") ; } } return 0 ; }
2).结果及心得
(1).查看本机IP
(2).ping网关IP
(3).ping本机IP
(4).ping局域网内IP
(5).问题与解决方案
①.问题:telnet是23端口,ssh是22端口,那么ping是什么端口?
答:ping基于ICMP,是在网络层运行的。而端口号为传输层的内容。所以在ICMP中根本就不需要关注端口号这样的信息。
②.Win7、win10 在VC6.0运行时WSASocket 返回错误 10013
3).完整代码
#include<stdio.h> #include<Winsock2.h> #include<ws2tcpip.h> #include<stdlib.h> #include<malloc.h> #include<string.h> #pragma comment(lib , "Ws2_32.lib") #define ICMP_ECHO_REQUEST 8 //定义回显请求类型 #define DEF_ICMP_DATA_SIZE 20 //定义发送数据长度 #define DEF_ICMP_PACK_SIZE 32 //定义数据包长度 #define MAX_ICMP_PACKET_SIZE 1024 //定义最大数据包长度 #define DEF_ICMP_TIMEOUT 3000 //定义超时为3秒 #define ICMP_TIMEOUT 11 //ICMP超时报文 #define ICMP_ECHO_REPLY 0 //定义回显应答类型 /* *IP报头结构 */ typedef struct { byte h_len_ver ; //IP版本号 byte tos ; // 服务类型 unsigned short total_len ; //IP包总长度 unsigned short ident ; // 标识 unsigned short frag_and_flags ; //标志位 byte ttl ; //生存时间 byte proto ; //协议 unsigned short cksum ; //IP首部校验和 unsigned long sourceIP ; //源IP地址 unsigned long destIP ; //目的IP地址 } IP_HEADER ; /* *定义ICMP数据类型 */ typedef struct _ICMP_HEADER { byte type ; //类型-----8 byte code ; //代码-----8 unsigned short cksum ; //校验和------16 unsigned short id ; //标识符-------16 unsigned short seq ; //序列号------16 unsigned int choose ; //选项-------32 } ICMP_HEADER ; typedef struct { int usSeqNo ; //记录序列号 DWORD dwRoundTripTime ; //记录当前时间 byte ttl ; //生存时间 in_addr dwIPaddr ; //源IP地址 } DECODE_RESULT ; /* *产生网际校验和 */ unsigned short GenerateChecksum(unsigned short *pBuf , int iSize) { unsigned long cksum = 0 ; //开始时将网际校验和初始化为0 while(iSize > 1) { cksum += *pBuf++ ; //将待校验的数据每16位逐位相加保存在cksum中 iSize -= sizeof(unsigned short) ; //每16位加完则将带校验数据量减去16 } //如果待校验的数据为奇数,则循环完之后需将最后一个字节的内容与之前结果相加 if(iSize) { cksum += *(unsigned char*)pBuf ; } //之前的结果产生了进位,需要把进位也加入最后的结果中 cksum = (cksum >> 16) + (cksum & 0xffff) ; cksum += (cksum >> 16) ; return (unsigned short)(~ cksum) ; } /* *对ping应答信息进行解析 */ boolean DecodeIcmpResponse_Ping(char *pBuf , int iPacketSize , DECODE_RESULT *stDecodeResult) { IP_HEADER *pIpHrd = (IP_HEADER*)pBuf ; int iIphedLen = 20 ; if(iPacketSize < (int)(iIphedLen + sizeof(ICMP_HEADER))) { printf("size error! ") ; return 0 ; } //指针指向ICMP报文的首地址 ICMP_HEADER *pIcmpHrd = (ICMP_HEADER*)(pBuf + iIphedLen) ; unsigned short usID , usSeqNo ; //获得的数据包的type字段为ICMP_ECHO_REPLY,即收到一个回显应答ICMP报文 if(pIcmpHrd->type == ICMP_ECHO_REPLY) { usID = pIcmpHrd->id ; //接收到的是网络字节顺序的seq字段信息 , 需转化为主机字节顺序 usSeqNo = ntohs(pIcmpHrd->seq) ; } if(usID != GetCurrentProcessId() || usSeqNo != stDecodeResult->usSeqNo) { printf("usID error! ") ; return 0 ; } //记录对方主机的IP地址以及计算往返的时延RTT if(pIcmpHrd->type == ICMP_ECHO_REPLY) { stDecodeResult->dwIPaddr.s_addr = pIpHrd->sourceIP ; stDecodeResult->ttl = pIpHrd->ttl ; stDecodeResult->dwRoundTripTime = GetTickCount() - stDecodeResult->dwRoundTripTime ; return 1 ; } return 0 ; } void Ping(char *IP) { unsigned long ulDestIP = inet_addr(IP) ; //将IP地址转化为长整形 if(ulDestIP == INADDR_NONE) { //转化不成功时按域名解析 HOSTENT *pHostent = gethostbyname(IP) ; if(pHostent) { ulDestIP = (*(IN_ADDR*)pHostent->h_addr).s_addr ; //将HOSTENT转化为长整形 } else { printf("TIMEOUT ") ; return ; } } //填充目的Socket地址 SOCKADDR_IN destSockAddr ; //定义目的地址 ZeroMemory(&destSockAddr , sizeof(SOCKADDR_IN)) ; //将目的地址清空 destSockAddr.sin_family = AF_INET ; destSockAddr.sin_addr.s_addr = ulDestIP ; destSockAddr.sin_port = htons(0); //初始化WinSock WORD wVersionRequested = MAKEWORD(2,2); WSADATA wsaData; if(WSAStartup(wVersionRequested,&wsaData) != 0) { printf("初始化WinSock失败! ") ; return ; } //使用ICMP协议创建Raw Socket SOCKET sockRaw = WSASocket(AF_INET , SOCK_RAW , IPPROTO_ICMP , NULL , 0 , WSA_FLAG_OVERLAPPED) ; if(sockRaw == INVALID_SOCKET) { printf("创建Socket失败 ! ") ; return ; } //设置端口属性 int iTimeout = DEF_ICMP_TIMEOUT ; if(setsockopt(sockRaw , SOL_SOCKET , SO_RCVTIMEO , (char*)&iTimeout , sizeof(iTimeout)) == SOCKET_ERROR) { printf("设置参数失败! ") ; return ; } if(setsockopt(sockRaw , SOL_SOCKET , SO_SNDTIMEO , (char*)&iTimeout , sizeof(iTimeout)) == SOCKET_ERROR) { printf("设置参数失败! ") ; return ; } //定义发送的数据段 char IcmpSendBuf[DEF_ICMP_PACK_SIZE] ; //填充ICMP数据包个各字段 ICMP_HEADER *pIcmpHeader = (ICMP_HEADER*)IcmpSendBuf; pIcmpHeader->type = ICMP_ECHO_REQUEST ; pIcmpHeader->code = 0 ; pIcmpHeader->id = (unsigned short)GetCurrentProcessId() ; memset(IcmpSendBuf + sizeof(ICMP_HEADER) , 'E' , DEF_ICMP_DATA_SIZE) ; //循环发送四个请求回显icmp数据包 int usSeqNo = 0 ; DECODE_RESULT stDecodeResult ; while(usSeqNo <= 3) { pIcmpHeader->seq = htons(usSeqNo) ; pIcmpHeader->cksum = 0 ; pIcmpHeader->cksum = GenerateChecksum((unsigned short*)IcmpSendBuf , DEF_ICMP_PACK_SIZE) ; //生成校验位 //记录序列号和当前时间 stDecodeResult.usSeqNo = usSeqNo ; stDecodeResult.dwRoundTripTime = GetTickCount() ; //发送ICMP的EchoRequest数据包 if(sendto(sockRaw , IcmpSendBuf , DEF_ICMP_PACK_SIZE , 0 , (SOCKADDR*)&destSockAddr , sizeof(destSockAddr)) == SOCKET_ERROR) { //如果目的主机不可达则直接退出 if(WSAGetLastError() == WSAEHOSTUNREACH) { printf("目的主机不可达! ") ; exit(0) ; } } SOCKADDR_IN from ; int iFromLen = sizeof(from) ; int iReadLen ; //定义接收的数据包 char IcmpRecvBuf[MAX_ICMP_PACKET_SIZE] ; while(1) { iReadLen = recvfrom(sockRaw , IcmpRecvBuf , MAX_ICMP_PACKET_SIZE , 0 , (SOCKADDR*)&from , &iFromLen) ; if(iReadLen != SOCKET_ERROR) { if(DecodeIcmpResponse_Ping(IcmpRecvBuf , sizeof(IcmpRecvBuf) , &stDecodeResult)) { printf("来自 %s 的回复: 字节 = %d 时间 = %dms TTL = %d " , inet_ntoa(stDecodeResult.dwIPaddr) , iReadLen - 20,stDecodeResult.dwRoundTripTime ,stDecodeResult.ttl) ; } break ; } else if(WSAGetLastError() == WSAETIMEDOUT) { printf("time out ! ***** ") ; break ; } else { printf("发生未知错误! ") ; break ; } } usSeqNo++ ; } //输出屏幕信息 printf("Ping complete... ") ; closesocket(sockRaw) ; WSACleanup() ; } int main() { char com[10] , IP[20] ; while(1){ printf("command>>") ; scanf("%s %s" , com , IP) ; if(strcmp(com , "ping") == 0) { Ping(IP) ; } else { printf("输入错误 ! ") ; } } return 0 ; }