ping的原理及实现
目录
1、介绍
ping命令是用来查看网络上另一个主机系统的网络连接是否正常的一个工具。
2、原理
ping的原理主要涉及ICMP协议(稍后介绍ICMP协议),它的大概过程如下:
- 假设主机A(192.168.1.2)向主机B(192.168.1.3)检测网络是否可达
- 主机A构建一个ICMP请求数据包,然后将ICMP数据包以及目标IP地址(192.168.1.3)一并交给IP层
- IP层查看是否有该目标IP的MAC地址,如果查找到MAC地址则构建IP数据包并发送;反之则发送ARP请求,通过ARP应答获取到MAC地址,然后构建IP数据包并发送
- 主机B接收到数据包后,构建一个ICMP回显数据包,并发送给主机A
- 主机A接收到ICMP回显数据包后,计算延迟、ttl等数据,并显示
2.1、ICMP协议
2.1.1、ICMP介绍
ICMP的全称为Internet Control Message Protocal,是网络控制协议,它在网络中的主要作用为:
- 主机探测
- 路由维护
- 路由选择
- 流量控制
2.1.2、ICMP头部
类型:占一字节,标识ICMP报文的类型,从类型值来看ICMP报文可以分为两大类。第一类是取值为1~127的差错报文,第2类是取值128以上的信息报文
代码:占一字节,标识对应ICMP报文的代码。它与类型字段一起共同标识了ICMP报文的详细类型
校验和:占二字节,这是对包括ICMP报文数据部分在内的整个ICMP数据报的校验和,以检验报文在传输过程中是否出现了差错
数据:根据报文种类不同,数据内容也不同
2.1.3、ICMP可能的消息列表
2.2、ping具体流程
2.2.1、在同一广播域内
假设主机A和主机B在同一广播域内,如图:
主机A想要去ping主机B
首先,主机A会查看本机MAC地址表中,是否有主机B(查找目标IP:192.168.1.3对应的MAC地址)的MAC地址,如果没有,则会主机A会向外发出ARP广播。
主机A获取到目标IP的MAC地址后,则开始发送ICMP报文,格式如下:
目标MAC | 源MAC | ... | 目标IP | 源IP | ... | ICMP报文 |
32-31-00-00-00-12 | 32-31-00-00-00-11 | 192.168.1.3 | 192.168.1.2 | Echo Request |
主机B收到ICMP报文后,于是回复
目标MAC | 源MAC | ... | 目标IP | 源IP | ... | ICMP报文 |
32-31-00-00-00-11 | 32-31-00-00-00-12 | 192.168.1.2 | 192.168.1.3 | Echo Answer |
主机A收到主机B的应答后,开始计算延迟、ttl等信息
2.2.2、在不同广播域
假设主机A和主机B在不同广播域内,如图:
主机A想要ping主机B
主机A想要ping主机B,则要先去找路由器转发,如果不知道路由器Mac地址,则会发送ARP报文,寻找路由器地址。
主机A获取到网关地址后,发送ICMP报文至路由器:
目标MAC | 源MAC | ... | 目标IP | 源IP | ... | ICMP报文 |
32-31-00-00-00-13 | 32-31-00-00-00-11 | 192.168.2.2 | 192.168.1.2 | Echo Request |
路由器获取到主机A的报文后,发现目的地址的IP为192.168.2.2,查询路由表发现,得找一个出去的接口,于是去掉原来的MAC地址头,加上自己的MAC地址头并且向主机B转发(如果路由器未找到主机B的MAC,则发送ARP报文询问):
目标MAC | 源MAC | ... | 目标IP | 源IP | ... | ICMP报文 |
32-31-00-00-00-12 | 32-31-00-00-00-14 | 192.168.2.2 | 192.168.1.2 | Echo Request |
主机B获取到ICMP报文后,则回复:
目标MAC | 源MAC | ... | 目标IP | 源IP | ... | ICMP报文 |
32-31-00-00-00-14 | 32-31-00-00-00-12 | 192.168.1.2 | 192.168.2.2 | Echo Answer |
路由器收到回复后,将源MAC和目标MAC修改,并转发给主机A:
目标MAC | 源MAC | ... | 目标IP | 源IP | ... | ICMP报文 |
32-31-00-00-00-11 | 32-31-00-00-00-13 | 192.168.1.2 | 192.168.2.2 | Echo Answer |
主机A收到主机B的应答后,开始计算延迟、ttl等信息
3、代码实现分析
1、主函数建立两个线程,一个接收ICMP数据包,一个发送ICMP数据包
#include "main.h"
PingPacket *ping = NULL;
int main(int argc, char const *argv[])
{
if(argc < 2)
{
printf("参数过少");
return -1;
}
char *dest_ip = (char *)argv[1];//获取目标地址
struct protoent *protocol = getprotobyname("icmp");//获取协议
if(protocol == NULL)
{
perror("getprotobyname()");
return -1;
}
ping = initPingPacket(protocol, dest_ip);//初始化ping变量
//截取信号SIGINT,将icmpSigint挂接上
signal(SIGINT, sigint);
pthread_t send_id, recv_id;//建立两个线程,用于发送和接收
if(pthread_create(&send_id, NULL, ping->sendIcmp, ping) < 0)//发送
{
return -1;
}
if(pthread_create(&recv_id, NULL, ping->recvIcmp, ping) < 0)//接收
{
return -1;
}
//等待线程结束
pthread_join(send_id, NULL);
pthread_join(recv_id, NULL);
delPingPacket(&ping);
}
void sigint(int signo)
{
ping->is_run = false;//关闭循环
}
2、发送、接受、打包、解包、计算校验和的数据结构
#include <stdbool.h>
#include <pthread.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <string.h>
#include <zconf.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <errno.h>
#include <netinet/ip_icmp.h>
#include "PacketStateArray.h"
typedef struct
{
int packet_send_count;//记录发送数据包数量
int packet_recv_count;//记录收到数据包数量
struct timeval begin;//ping程序开始时间
pid_t pid;//程序pid
char *dest_str;//目标ip字符串
int rawsock;
struct sockaddr_in dest;//目的地址
bool is_run;//是否在运行
unsigned char send_buff[72];
unsigned char recv_buff[2048];//防止接收溢出,设置大一些
PacketStateArray *packets;//数据包状态数组
void (*sendIcmp) (void *this);//发送ICMP数据包线程函数
void (*recvIcmp) (void *this);//接受ICMP数据包线程函数
unsigned short (*checkSum) (unsigned char *data, int length);//校验和函数
void (*packIcmp) (void *this, PacketState *packet_state, int length);//打包ICMP数据包函数
void (*unpackIcmp) (void *this, int length);//解包函数
} PingPacket;
PingPacket *initPingPacket(struct protoent *protocol, char *dest_str);
void delPingPacket(PingPacket **this);
void sendIcmp(PingPacket *this);
void recvIcmp(PingPacket *this);
unsigned short checkSum(unsigned char *data, int length);
void packIcmp(PingPacket *this, PacketState *packet, int length);
void unpackIcmp(PingPacket *this, int length);
struct timeval timevalSub(struct timeval end, struct timeval begin);//计算时间差函数
3、存放已发送的ICMP数据包结构
#include <sys/time.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
struct timeval begin;//数据包发送时间
struct timeval end;//数据包接受时间
int seq;//数据包***
short flag;//当前位置是否被占用
} PacketState;
typedef struct
{
PacketState *packet_state;//数据包状态数组
int index;//数组尾指针
int size;//数组大小
PacketState* (*next) (void *this);//获取下一个空位置函数
} PacketStateArray;
PacketStateArray *initPacketStateArray(int size);
void delPacketStateArray(PacketStateArray **this);
PacketState* next(PacketStateArray *this);
4.运行截图
5、工程文件
还在审核中
推荐阅读
-
图解二叉树的三种遍历方式及java实现代码
-
python实现合并多个list及合并多个django QuerySet的方法示例
-
angular.js实现数据双向通信的原理详细介绍
-
Python的Asyncore异步Socket模块及实现端口转发的例子
-
Python实现TCP/IP协议下的端口转发及重定向示例
-
如何在开启IE浏览器时同时打开多个网页实现原理及方法
-
解析android中隐藏与显示软键盘及不自动弹出键盘的实现方法
-
Android编程实现获取标题栏、状态栏的高度、屏幕大小及模拟Home键的方法
-
Jquery 点击按钮自动高亮实现原理及代码
-
Android HandlerThread的使用及原理详解