【⽹络编程2】⽹络编程基础-发送ICMP包(Ping程序)
IP协议
⽹络地址和主机协议
位于⽹络层的协议,主要⽬的是使得⽹络能够互相通信,该协议使⽤逻辑地址跨⽹络通信,⽬前有两个版本IPV4,IPV6。
在IPV4协议中IP地址是⼀个32位的数备,采⽤点分四组的表⽰法便于使⽤。每个IP地址包含两个部分,⽹络地址和主机地址。
⽹络地址和主机地址的划分由⼦⽹掩码来决定。⽹络地址⽤来标⽰所连接到的局域⽹,主机地址则标⽰设备本⾝,⼦⽹掩码与IP地址等长,被设为1的部分标⽰IP地址的对应部分为⽹络地址,设为0则标⽰IP对应位为主机地址。
IP格式
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报⽂如所⽰,不现类型由报⽂中的类型字段和代码字段来共同决定。
常见的类型和代码有
ICMP回显(echo)请求和辉县应答报⽂格式
ICMP地址掩码请求和应答
ICMP时间戳请求与应答
ICMP不可达报⽂、ICMP超时报⽂
ICMP重定向报⽂
ICMP路由器请求报⽂格式
ICMP路由器通告报⽂格式
ICMP源站抑制差错报⽂格式
等等。。。。。
编程操作
因为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;
发送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();
pe = 8;
de = 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 \n ");
}
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;
}
// 计算校验和(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位整型(截断)后返回
使⽤原始套接字编程调试的时候运⾏权限不够,是⽆法运⾏的。
可以在VS⾥做设置,在需要提升权限的时候⾃动重启VS提升权限。
点击项⽬右键->属性->配置属性->链接器->清单⽂件->UAC执⾏级别->requireAdministrator(选择)。
将上⾯的代码与思路整合就成了下⾯的实例代码;
#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请求时记录发送时间,当接受到应答数据时,在⽤Get
Tickcount()- echoRequest.time这样就能得到请求应答需要多少时间了。 echoRequest.time = GetTickCount();
pe = 8;
de = 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 \n ");
}
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++)
socket通信报文格式
{
/
/发送ICMP数据
sendEchoReQuest(sock, dstIP);
//接收ICMP数据
ECHORESPONSE sponse;
recvEchoReQuest(sock, &sponse, &dstIP);
printf("来⾃ %s 的回复: 字节=32 时间=%dms TTL=%d \n",
inet_ntoa(dstIP.sin_addr),
GetTickCount() - hoRequest.time,
l
);
}
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。