zoukankan      html  css  js  c++  java
  • 【网络编程2】网络编程基础-发送ICMP包(Ping程序)

    IP协议

    网络地址和主机协议

    位于网络层的协议,主要目的是使得网络能够互相通信,该协议使用逻辑地址跨网络通信,目前有两个版本IPV4,IPV6。

    在IPV4协议中IP地址是一个32位的数备,采用点分四组的表示法便于使用。每个IP地址包含两个部分,网络地址和主机地址。

    网络地址和主机地址的划分由子网掩码来决定。网络地址用来标示所连接到的局域网,主机地址则标示设备本身,子网掩码与IP地址等长,被设为1的部分标示IP地址的对应部分为网络地址,设为0则标示IP对应位为主机地址。

    IP格式

    image

    0-3 版本:IP所使用的版本

    4-7 首部长度:IP头的长度(4字节的整数倍)

    8-15 服务类型:路由器使用该字段做流量优先排序

    16-18 | 19-31 总长度:IP长度+数据包长度

    32 标示符:一个唯一标示,用来区分数据包或数据分片的顺序

    32 标记:标示数据包是否分片数据

    32 分片偏移:分片数据时该字段有效,用于将数据包按顺序组合

    64 存活时间:定义数据包的生存周期,路由器中转一次该值减一

    64 协议:用来识别数据包上层协议类型

    64 首部校验和:错误检测机制,确认内容是否损坏或被篡改

    96 源IP地址:发送方主机的IP地址

    128 目的IP地址:接收方主机的IP地址

    160 选项:可选

    160 or 192+ 数据:使用IP传递的实际数据

    ICMP协议

    IP协议并不是一个可靠的协议,它不保证数据被送达,那么保证数据送达的工作应该由其他的模块来完成。其中一个重要的模块就是ICMP(网络控制报文)协议。

    ICMP(internet control message protocol)是internet控制报文协议。是TCP/IP协议族里的一个子协议,用于在IP主机、路由器之间传递控制信息。

    控制信息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。

    ICMP协议大致分为两类,一种是查询报文,一种是差错报文。查询报文由发送者发出,差错报文是由出错的主机返回发给源数据包的发送者。

    PING可以说是ICMP的最著名的应用,系统自带工具当某一个网站上不去的时候。通常会ping一下这个网站,ping会回显一些有用的信息。

    ICMP协议的格式

    ICMP头相对较小并根据用途而改变,ICMP报文的前4个字节是统一的格式,共有三个字段:即类型,代码和检验和。

    ICMP报文类型
    各种类型的ICMP报文如所示,不现类型由报文中的类型字段和代码字段来共同决定。

    image

    常见的类型和代码有

    • ICMP回显(echo)请求和辉县应答报文格式

    • ICMP地址掩码请求和应答

    • ICMP时间戳请求与应答

    • ICMP不可达报文、ICMP超时报文

    • ICMP重定向报文

    • ICMP路由器请求报文格式

    • ICMP路由器通告报文格式

    • ICMP源站抑制差错报文格式

    等等。。。。。

    ICMP地址掩码请求和应答

    image

    ICMP时间戳请求与应答

    image

    编程操作

    1、定义结构体

    因为ICMP是基于IP协议运行的,所以我们要先定义IP格式的结构体;
    以下的是三个结构体分别对应了IP、ICMP请求、ICMP应答的发包格式。。

    定义IP首部格式

    //定义IP首部格式
    typedef struct  _IPHeader
    {
    	u_char vile;           // 版本和首部长度
    	u_char ser;            // 服务类型
    	u_short totalLen;      // 总长度
    	u_short id;            // 标示符
    	u_short flag;          // 标记+分片偏移
    	u_char ttl;            // 存活时间
    	u_char protocol;       // 协议
    	u_short checkSum;      // 首部校验和
    	in_addr srcIP;         // 源IP地址
    	in_addr destIP;        // 目的IP地址
    }IPHeader, *PIPHDR;
    
    

    定义ICMP头部格式

    //ICMP头部
    typedef struct _ICMPHeader
    {
    	u_char type;            // 类型
    	u_char code;            // 代码
    	u_short checkSum;       // 校验和
    	u_short id;             // 标示符
    	u_short seq;            // 序列号
    }ICMPHeader, *PICMPHDR;
    
    

    ICMP时间戳请求报文格式

    //ICMP时间戳请求报文
    typedef struct _ECHOREQUEST
    {
    	ICMPHeader icmpHeader;       //ICMP头部
    	int time;                    //记录ping时间  
    	char data[32];               //数据
    }ECHOREQUEST, *pechorequest;
    

    ICMP时间戳应答报文格式

    //ICMP时间戳应答报文
    typedef struct _ECHORESPONSE
    {
    	IPHeader ipHeader;
    	ECHOREQUEST echoRequest;
    	char fill[255];            //接收其他多余的应答数据
    
    }ECHORESPONSE, *PECHORESPONSE;
    
    

    2、ICMP发送和接收

    发送ICMP数据包函数

    将请求的包的宽度写入结构体中,构造出ICMP的请求格式向目标服务器发送数据。

    //发送ICMP数据包 sendEchoReQuest
    void sendEchoReQuest(SOCKET sock, sockaddr_in dstIP)
    {
    	static int id = 1;
    	static int seq = 1;
    	
    	//ICMP请求
    	ECHOREQUEST echoRequest = { 0 };
    	//主要是用来记录请求应答的时间,当发送ECHO请求时记录发送时间,当接受到应答数据时,在用GetTickcount()- echoRequest.time这样就能得到请求应答需要多少时间了。
    	echoRequest.time = GetTickCount();   
    	echoRequest.icmpHeader.type = 8;
    	echoRequest.icmpHeader.code = 0;
    	echoRequest.icmpHeader.id = id++;
    	echoRequest.icmpHeader.seq = seq++;
    	echoRequest.icmpHeader.checkSum =
    		checksum((u_short*)&echoRequest, sizeof(echoRequest));
    
    	int re = sendto(sock,
    		(char *)&echoRequest,
    		sizeof(echoRequest), 
    		0, 
    		(sockaddr*)&dstIP, 
    		sizeof(dstIP));
    
    	if (re == SOCKET_ERROR)
    	{
    		printf(" send error 
     ");
    	}
    	return;
    }
    
    

    接收ICMP数据包函数

    有对应的发送数据包动作,对方服务器如果存活那么会发回来响应包;

    //接收ICMP数据包 recvEchoReQuest
    void recvEchoReQuest(SOCKET sock,
    	ECHORESPONSE * sponse, sockaddr_in *dstIP) 
    {
    	int size = sizeof(sockaddr);
    	int re = recvfrom(sock, (char *)sponse, sizeof(ECHORESPONSE), 0,
    		(sockaddr*)dstIP, &size);
    	if (re == SOCKET_ERROR)
    	{
    		printf(" recvfrom error");
    	}
    	return;
    }
    

    附录A:如何计算检验和

    // 计算校验和(16位二进制反码求和)
    u_short checksum(u_short *buffer, int len)
    {
    	register int nleft = len;
    	register u_short *w = buffer;
    	register u_short answer;
    	register int sum = 0;
    	// 使用32bit的累加器,进行16bit的反馈计算
    	while (nleft > 1) {
    		sum += *w++;
    		nleft -= 2;
    	}
    	// 补全奇数位
    	if (nleft == 1) {
    		u_short	u = 0;
    
    		*(u_char *)(&u) = *(u_char *)w;
    		sum += u;
    	}
    	// 将反馈的16bit从高位移至地位
    	sum = (sum >> 16) + (sum & 0xffff);	/* add hi 16 to low 16 */
    	sum += (sum >> 16);					/* add carry */
    	answer = ~sum;						/* truncate to 16 bits */
    	return (answer);
    }
    
    

    ICMP中检验和的计算算法为:

    1、将检验和字段置为0

    2、把需校验的数据看成以16位为单位的数字组成,依次进行二进制反码求和

    3、把得到的结果存入检验和字段中

    所谓二进制反码求和,就是:

    1、将源数据转成反码

    2、0+0=0 0+1=1 1+1=0进1

    3、若最高位相加后产生进位,则最后得到的结果要加1

    在实际实现的过程中,比较常见的代码写法是:

    1、将检验和字段置为0

    2、把需校验的数据看成以16位为单位的数字组成,依次进行求和,并存到32位的整型中

    3、把求和结果中的高16位(进位)加到低16位上,如果还有进位,重复第3步[实际上,这一步最多会执行2次]

    4、将这个32位的整型按位取反,并强制转换为16位整型(截断)后返回

    附录B VS2015 UAC设置

    使用原始套接字编程调试的时候运行权限不够,是无法运行的。

    可以在VS里做设置,在需要提升权限的时候自动重启VS提升权限。

    点击项目右键->属性->配置属性->链接器->清单文件->UAC执行级别->requireAdministrator(选择)。

    image

    实例代码

    将上面的代码与思路整合就成了下面的实例代码;

    #include "stdafx.h"
    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #include "data.h"
    
    
    //发送ICMP数据包 sendEchoReQuest
    void sendEchoReQuest(SOCKET sock, sockaddr_in dstIP)
    {
    	static int id = 1;
    	static int seq = 1;
    	
    	//ICMP请求
    	ECHOREQUEST echoRequest = { 0 };
    	//主要是用来记录请求应答的时间,当发送ECHO请求时记录发送时间,当接受到应答数据时,在用GetTickcount()- echoRequest.time这样就能得到请求应答需要多少时间了。
    	echoRequest.time = GetTickCount();   
    	echoRequest.icmpHeader.type = 8;
    	echoRequest.icmpHeader.code = 0;
    	echoRequest.icmpHeader.id = id++;
    	echoRequest.icmpHeader.seq = seq++;
    	echoRequest.icmpHeader.checkSum =
    		checksum((u_short*)&echoRequest, sizeof(echoRequest));
    
    	int re = sendto(sock,
    		(char *)&echoRequest,
    		sizeof(echoRequest), 
    		0, 
    		(sockaddr*)&dstIP, 
    		sizeof(dstIP));
    
    	if (re == SOCKET_ERROR)
    	{
    		printf(" send error 
     ");
    	}
    	return;
    }
    
    
    //接收ICMP数据包 recvEchoReQuest
    void recvEchoReQuest(SOCKET sock,
    	ECHORESPONSE * sponse, sockaddr_in *dstIP) 
    {
    	int size = sizeof(sockaddr);
    	int re = recvfrom(sock, (char *)sponse, sizeof(ECHORESPONSE), 0,
    		(sockaddr*)dstIP, &size);
    	if (re == SOCKET_ERROR)
    	{
    		printf(" recvfrom error");
    	}
    	return;
    }
    
    
    //ping函数
    void Ping(char * host) 
    {
    	//2.创建套接字
    	SOCKET sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    
    
    	//获取域名对应的IP
    	HOSTENT * lpHost = gethostbyname(host);
    
    	sockaddr_in dstIP = { 0 };
    	dstIP.sin_family = AF_INET;
    	dstIP.sin_addr.S_un.S_addr = *(u_long*)lpHost->h_addr;
    
    	for (int i=0;i<4;i++)
    	{
    		//发送ICMP数据
    		sendEchoReQuest(sock, dstIP);
    
    		//接收ICMP数据
    		ECHORESPONSE sponse;
    		recvEchoReQuest(sock, &sponse, &dstIP);
    
    		printf("来自 %s 的回复: 字节=32 时间=%dms TTL=%d 
    ",
    			inet_ntoa(dstIP.sin_addr),
    			GetTickCount() - sponse.echoRequest.time,
    			sponse.ipHeader.ttl
    		);
    	}
    }
    
    int main()
    {
    	//1.初始化winsock环境
    	WSADATA wsa;
    	WSAStartup(MAKEWORD(2, 2),&wsa);
    
    	while (true)
    	{
    		Ping("www.baidu.com");
    		char in = getchar();
    		if (in == 'q')
    		{
    			break;
    		}
    
    	}
    	//2.释放环境
    	WSACleanup();
    
        return 0;
    }
    
    

    参考:
    C++实现Ping
    http://www.cnblogs.com/goagent/p/4078940.html
    给VS程序添加管理员权限等
    http://www.voidcn.com/blog/wokaowokaowokao12345/article/p-5973906.html

  • 相关阅读:
    Ajax World 名人堂
    Chrome Flash插件的手动安装方法
    2009年郭红俊的工作
    动画缓冲或叫缓动函数(Animation Easing)
    2008年郭红俊的工作
    Android下调整多媒体音量方法
    游戏中按概率播放某个音效简单c++实现
    string转int 等(转)
    .NET简谈面“.NET技术”向接口编程 狼人:
    改善代码设计 —— 处理概括关系(Dealing w“.NET技术”ith Generalization) 狼人:
  • 原文地址:https://www.cnblogs.com/17bdw/p/6158761.html
Copyright © 2011-2022 走看看