作用
Linux ping 命令用于检测主机:执行 ping 会使用 ICMP 传输协议,发出要求回应的信息,若远端主机的网络功能没有问题,就会回应该信息,因而得知该主机运作正常。
基础使用
#ping 192.168.1.1//ping 主机ip
#ping -c 2 www.runoob.com//收到两次包后,自动退出
#ping -i 3 -s 1024 -t 255 g.cn //-i 3 发送周期为 3秒 -s 设置发送包的大小 -t 设置TTL值为 255
说明:
因为本文是偏linux ping编程,对于ping的用法介绍的很简单,关于详细的ping的介绍与使用,还请自行补充学习。
正文开始
- 先注册一个信号(虽然对于本文没什么关系,但是想增加下信号量的练习):
#include <signal.h>
typedef void (*sighandler)(int);
/*注册一个signal*/
void handler_func(int signumber)
{
printf("[%s][%d]------ signumber:%d--------\n",__func__,__LINE__,signumber);
}
signal(SIGINT,handler_func);
- ICMP协议理解:要进行PING的开发,我们首先需要知道PING的实现是基于ICMP协议来开发的。要进行PING的开发,我们首先需要知道PING的实现是基于ICMP协议来开发的。
报文格式
图形格式
对比着看,ICMP报文的种类有两种,即ICMP差错报告报文和ICMP询问报文。PING程序使用的ICMP报文种类为ICMP询问报文。注意一下上面说到的ICMP报文格式中的“类型”字段,我们在组包的时候可以向该字段填写不同的值来标定该ICMP报文的类型。下面列出的是几种常用的ICMP报文类型:
每个字段的字节大小如下:
3. ICMP包的组装
void combin_Internet_contrl_message_protocol(struct icmp* message,int seq,int lenth)
{
int i = 0;
message->icmp_type = ICMP_ECHO;
message->icmp_code = 0;
message->icmp_cksum = 0;
message->icmp_seq = seq;
message->icmp_id = pid & 0xffff;
message->icmp_cksum = check_sum((unsigned short*)message, lenth);
printf("-----icmp_type:%d-------\n",message->icmp_type);
printf("-----icmp_code:%d-------\n",message->icmp_code);
printf("-----check_sum:%#X-------\n",message->icmp_cksum);
printf("-----icmp_seq:%d-------\n",message->icmp_seq);
printf("-----icmp_id:%d-------\n",message->icmp_id);
printf("-----icmp_data[1]:%d-------\n",message->icmp_data[1]);
printf("-----icmp_data[2]:%d-------\n",message->icmp_data[2]);
printf("-----icmp_data[3]:%d-------\n",message->icmp_data[3]);
printf("-----icmp_data[4]:%d-------\n",message->icmp_data[4]);
printf("-----icmp_data[63]:%d-------\n",message->icmp_data[63]);
}
icmp_cksum 必须先填写为0再执行校验和算法计算,否则ping时对方主机会因为校验和计算错误而丢弃请求包,导致ping的失败。
在代码中我们看到了校验和check_sum,算法的基本思路:
采用的都是将数据流视为16位整数流进行重复叠加计算。为了计算检验和,首先把检验和字段置为0。然后,对有效数据范围内中每个16位进行二进制反码求和,结果存在检验和字段中,如果数据长度为奇数则补一字节0。当收到数据后,同样对有效数据范围中每个16位数进行二进制反码的求和。由于接收方在计算过程中包含了发送方存在首部中的检验和,因此,如果首部在传输过程中没有发生任何差错,那么接收方计算的结果应该为全0或全1(具体看实现了,本质一样)如果结果不是全0或全1,那么表示数据错误。
具体算法如下:
int check_sum(unsigned short *buf, int sz)
{
int nleft = sz;
int sum = 0;
unsigned short *w = buf;
unsigned short ans = 0;
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
if (nleft == 1) {
*(unsigned char *) (&ans) = *(unsigned char *) w;
sum += ans;
}
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
ans = ~sum;
return (ans);
}
- ICMP包的解包:知道怎么封装包,那解包就也不难了,注意的是,收到一个ICMP包,我们不要就认为这个包就是我们发出去的ICMP回送回答包,我们需要加一层代码来判断该ICMP报文的id和seq字段是否符合我们发送的ICMP报文的设置,来验证ICMP回复包的正确性。
int analyse_Internet_contrl_message_protocol(char* buf,int len)
{
int iphdr_len;
struct ip* ip_hdr = (struct ip *)buf;
iphdr_len = ip_hdr->ip_hl*4;
struct icmp* icmp = (struct icmp*)(buf+iphdr_len);
len-=iphdr_len; //icmp包长度
/*判断date长度是否为0,因为还有八字节的header*/
if(len < 8)
{
printf("---- data len is null\n----");
return -1;
}
if(icmp->icmp_type != ICMP_ECHOREPLY)
{
printf("--------------回复的不是reply-----------\n");
return -1;
}
/*判断包的序列号是否合理*/
if((icmp->icmp_seq < 0))
{
printf("------icmp packet seq is out of range!----------\n");
return -1;
}
/*判断该包是ICMP回送回答包且该包是我们发出去的*/
if(icmp->icmp_id == (pid & 0xffff))
{
printf("-----------getpid:%d---------\n",pid);
}
else
{
return -1;
}
return 1;
}
- 说明,因为代码中添加了信号量,所以ctrl+c不可以终止程序运行,需要用ctrl+\来终止
整体代码
#include <netdb.h>
#include<netinet/in.h>
#include <netinet/ip.h>
#include <signal.h>
#include <sys/time.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <netinet/ether.h>
#include <netpacket/packet.h>
#include <netdb.h>
#include <errno.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>
#include <signal.h>
typedef void (*sighandler)(int);
int fd = 0;
int pid;
int size = 1024*128;
struct protoent * ret;
struct sockaddr_in des;
unsigned int inaddr;
struct icmp * send_buf;
char recv_buf[512];
unsigned int SEQ = 0 ;
int check_sum(unsigned short *buf, int sz)
{
int nleft = sz;
int sum = 0;
unsigned short *w = buf;
unsigned short ans = 0;
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
if (nleft == 1) {
*(unsigned char *) (&ans) = *(unsigned char *) w;
sum += ans;
}
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
ans = ~sum;
return (ans);
}
void combin_Internet_contrl_message_protocol(struct icmp* message,int seq,int lenth)
{
int i = 0;
message->icmp_type = ICMP_ECHO;
message->icmp_code = 0;
message->icmp_cksum = 0;
message->icmp_seq = seq;
message->icmp_id = pid & 0xffff;
message->icmp_cksum = check_sum((unsigned short*)message, lenth);
printf("-----icmp_type:%d-------\n",message->icmp_type);
printf("-----icmp_code:%d-------\n",message->icmp_code);
printf("-----check_sum:%#X-------\n",message->icmp_cksum);
printf("-----icmp_seq:%d-------\n",message->icmp_seq);
printf("-----icmp_id:%d-------\n",message->icmp_id);
printf("-----icmp_data[1]:%d-------\n",message->icmp_data[1]);
printf("-----icmp_data[2]:%d-------\n",message->icmp_data[2]);
printf("-----icmp_data[3]:%d-------\n",message->icmp_data[3]);
printf("-----icmp_data[4]:%d-------\n",message->icmp_data[4]);
printf("-----icmp_data[63]:%d-------\n",message->icmp_data[63]);
}
int analyse_Internet_contrl_message_protocol(char* buf,int len)
{
int iphdr_len;
struct ip* ip_hdr = (struct ip *)buf;
iphdr_len = ip_hdr->ip_hl*4;
struct icmp* icmp = (struct icmp*)(buf+iphdr_len);
len-=iphdr_len; //icmp包长度
/*判断date长度是否为0,因为还有八字节的header*/
if(len < 8)
{
printf("---- data len is null\n----");
return -1;
}
if(icmp->icmp_type != ICMP_ECHOREPLY)
{
printf("--------------回复的不是reply-----------\n");
return -1;
}
/*判断包的序列号是否合理*/
if((icmp->icmp_seq < 0))
{
printf("------icmp packet seq is out of range!----------\n");
return -1;
}
/*判断该包是ICMP回送回答包且该包是我们发出去的*/
if(icmp->icmp_id == (pid & 0xffff))
{
printf("-----------getpid:%d---------\n",pid);
}
else
{
return -1;
}
return 1;
}
void handler_func(int signumber)
{
printf("[%s][%d]------ signumber:%d--------\n",__func__,__LINE__,signumber);
}
int main(void)
{
struct timeval tv;
tv.tv_usec = 0; /*设置select函数的超时时间为200us*/
tv.tv_sec = 3;
struct protoent *proto;
int rev_size1 = 0 ;
int ret;
fd_set read_fd;
/*注册一个signal*/
signal(SIGINT,handler_func);
proto = getprotobyname("icmp");
int cnt= 0 ;
if(proto == NULL)
{
printf("[%s][%d]-----------getprotobyname get is fail----\n",__func__,__LINE__);
return;
}
printf("-------------proto->p_proto:%d------\n",proto->p_proto);
/*创建socket */
fd = socket(AF_INET,SOCK_RAW,proto->p_proto);
if(fd < 0)
{
printf("[%s][%d]-----------fd is:%d,socket create is fail----\n",__func__,__LINE__,fd);
int eno = errno;
if(eno == 1)
{
/*必须使用sudo才能建立Raw socket*/
printf("Operation not permitted!\n");
}
else
{
/*除了权限之外的错误打印*/
printf("Cannot create socket! Error %d", eno);
}
return;
}
/*增大接收缓冲区至128K*/
printf("-----------setsockopt---------\n");
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
/*清空网络结构体*/
bzero(&des,sizeof(des));
/*网络结构体赋值ipv4*/
des.sin_family = AF_INET;
/*十进制的数转化为二进制的数,将字符串形式的IP地址转换为按网络字节顺序的整型值*/
inaddr = inet_addr("192.168.3.1");
printf("-----------------inet_addr结束------------\n");
/*将需要ping的ip地址赋值到des网络结构体*/
memcpy((char*)&des.sin_addr, &inaddr, sizeof(inaddr));
/*设置监听socket*/
FD_ZERO(&read_fd);
FD_SET(fd, &read_fd);
/*获取当前pid*/
pid = getpid()&0xffff;
printf("-----------getpid:%d---------\n",pid);
/*申请内存,并赋空*/
send_buf = malloc(64);
if(send_buf == NULL)
{
free(send_buf);
goto fail;
}
bzero(send_buf,sizeof(send_buf));
while(1)
{
/*组包*/
combin_Internet_contrl_message_protocol(send_buf,cnt,64);
printf("[%s][%d]-----------------组包结束------------\n",__func__,__LINE__);
/*sendto 函数*/
/*或者使用ret = sendto(fd, send_buf, 64, 0, (struct sockaddr*)&des, sizeof(struct sockaddr_in));*/
ret = sendto(fd,send_buf,64,0,(struct sockaddr *)&des,sizeof(des));
if(ret < 0 )
{
printf("------------send fail ---------\n");
}
/*发包数++*/
cnt++;
/*睡眠1s*/
sleep(1);
/*接受buff赋空*/
memset(recv_buf, 0 ,sizeof(recv_buf));
/*监视socket*/
printf("---------------正常进入----------------\n");
ret = select(fd+1, &read_fd, NULL, NULL, &tv);
switch(ret)
{
case 0://超时
printf("------------超时-----------\n");
break;
case -1://error
printf("------------错误-----------\n");
break;
default:
rev_size1 = recv(fd, recv_buf, sizeof(recv_buf), 0);
if(rev_size1 < 0)
{
printf("----------recv data fail!------------\n");
continue;//中止本次循环,重新判断循环条件,开始下一次循环
}
/*对接收的包进行解封*/
ret = analyse_Internet_contrl_message_protocol(recv_buf, size);
/*不是属于自己的icmp包,丢弃不处理*/
if(ret == -1)
{
continue;
}
printf("--------------ping success ----------\n");
break;
}
}
fail:
close(fd);
return 0;
}
编译与运行
代码可以直接gcc进行编译:
gcc ping.c -o ping.o
执行:因为存在权限问题,所以要root执行
sudo ./ping.o
运行结果:
抓包截图:成功抓取发送与接收
欢迎大家一起探讨交流,不吝指教hh