Linux网络编程---ICMP协议分析及ping程序实现Word文档下载推荐.docx
《Linux网络编程---ICMP协议分析及ping程序实现Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《Linux网络编程---ICMP协议分析及ping程序实现Word文档下载推荐.docx(18页珍藏版)》请在冰点文库上搜索。
u16id;
u16frag_off;
u8ttl;
u8protocol;
u16check;
u32saddr;
u32daddr;
};
⼆、ICMP协议
(1)ICMP消息类型
ICMP消息分为两⼤类,错误报告消息和查询消息,这⾥仅介绍查询消息,每个查询消息类型均包括⼀对请求和应答消息。
(2)ICMP消息通⽤格式
ICMP消息包括8字节的头部和变长数据两个部分,其中所有消息类型头部的前4个字节均相同,头部其余4个字节随消息的不同⽽不同。
如图所⽰:
ICMP消息头部的头4个字节分别是消息类型tye,消息代码code和校验和checksum,其中checksum字段包括头部和数据两部分,⽽并⾮仅头部,查询消息的数据部分data包含了⽤于查询所需要的额外数据。
(3)ICMP查询请求和应答消息格式
ICMP回应请求(echo-request)和应答消息(echo-reply)⽤于诊断两个系统(主机或路由器)之间是否能够进⾏通信,当其中⼀⽅发送回应请求消息给另⼀⽅时,接收到回应请求消息的主机或者路由器将以应答消息进⾏应答,常⽤的⽹络ping命令就是基于此消息类型的,如下图所⽰其中type字段为8表⽰回应请求,0表⽰应答,code字段暂未是要你管,为0.
(4)ICMP消息格式的C语⾔定义
structicmphdr
u8type;
u8code;
u16checksum;
union
struct
u16id;
u16sequence;
}echo;
u32gateway;
struct
u16unused;
u16mtu;
}frag;
//pmtu发现
}un;
//u32icmp_timestamp[2];
//时间戳
//ICMP数据占位符
u8data[0];
#defineicmp_idun.echo.id
#defineicmp_sequn.echo.sequence
ping程序实现:
#include<
stdio.h>
stdlib.h>
#include<
sys/time.h>
unistd.h>
string.h>
sys/socket.h>
sys/types.h>
netdb.h>
errno.h>
arpa/inet.h>
signal.h>
netinet/in.h>
#ifndef_LITTLE_ENDIAN_BITFIELD#define_LITTLE_ENDIAN_BITFIELD#endif
#defineIP_HSIZEsizeof(structiphdr)//定义IP_HSIZE为ip头部长度#defineIPVERSION4//定义IPVERSION为4,指出⽤ipv4
#defineICMP_ECHOREPLY0//Echo应答
#defineICMP_ECHO 8//Echo请求
#defineBUFSIZE1500 //发送缓存最⼤值
#defineDEFAULT_LEN56//ping消息数据默认⼤⼩
//数据类型别名
typedefunsignedcharu8;
typedefunsignedshortu16;
typedefunsignedintu32;
//ICMP消息头部structicmphdr
u32icmp_timestamp[2];
#defineICMP_HSIZEsizeof(structicmphdr)structiphdr
#ifdefined_LITTLE_ENDIAN_BITFIELDu8hlen:
#elifdefined_BIG_ENFIAN_BITFELDu8ver:
charhello[]="
hellothisisapingtest."
;
char*hostname;
//被ping的主机
intdatalen=DEFAULT_LEN;
//ICMP消息携带的数据长度
charsendbuf[BUFSIZE];
charrecvbuf[BUFSIZE];
intnsent;
//发送的ICMP消息序号
intnrecv;
pid_tpid;
//ping程序的进程pid
structtimevalrecvtime;
//收到ICMP应答的时间戳intsockfd;
//发送和接收原始套接字
structsockaddr_indest;
//被ping主机的ip
structsockaddr_infrom;
//发送ping应答消息的主机ip
structsigactionact_alarm;
structsigactionact_int;
//设置的时间是⼀个结构体,倒计时设置,重复倒时,超时值设为1秒structitimervalval_alarm;
//函数原型
voidalarm_handler(int);
//SIGALRM处理程序voidint_handler(int);
//SIGINT处理程序
voidset_sighandler();
//设置信号处理程序voidsend_ping();
//发送ping消息
voidrecv_reply();
//接收ping应答
u16checksum(u8*buf,intlen);
//计算校验和inthandle_pkt();
//ICMP应答消息处理
voidget_statistics(int,int);
//统计ping命令的检测结果voidbail(constchar*);
//错误报告
intmain(intargc,char**argv)//argc表⽰隐形程序命令⾏中参数的数⽬,argv是⼀个指向字符串数组指针,其中每⼀个字符对应⼀个参数
val_alarm.it_interval.tv_sec=1;
val_alarm.it_interval.tv_usec=0;
val_alarm.it_value.tv_sec=0;
val_alarm.it_value.tv_usec=1;
structhostent*host;
//该结构体属于include<
inton=1;
if((host=gethostbyname(argv[1]))==NULL)
{//gethostbyname()返回对应于给定主机名的包含主机名字和地址信息的结构指针,perror("
cannotunderstandthehostname"
);
//理解不了输⼊的地址
exit
(1);
}
hostname=argv[1];
//取出地址名
memset(&
dest,0,sizeofdest);
//将dest中前sizeof(dest)个字节替换为0并返回s,此处为初始化,给最⼤内存清零dest.sin_family=PF_INET;
//PF_INET为IPV4,internet协议,在<
中,地址族dest.sin_port=ntohs(0);
//端⼝号,ntohs()返回⼀个以主机字节顺序表达的数。
dest.sin_addr=*(structin_addr*)host->
h_addr_list[0];
//host->
h_addr_list[0]是地址的指针.返回IP地址,初始化
if((sockfd=socket(PF_INET,SOCK_RAW,IPPROTO_ICMP))<
0)
{//PF_INEI套接字协议族,SOCK_RAW套接字类型,IPPROTO_ICMP使⽤协议,调⽤socket函数来创建⼀个能够进⾏⽹络通信的套接字。
这⾥判断是否perror("
rawsocketcreatederror"
setuid(getuid());
//getuid()函数返回⼀个调⽤程序的真实⽤户ID,setuid()是让普通⽤户可以以root⽤户的⾓⾊运⾏只有root帐号才能运⾏的程序或命令。
pid=getpid();
//getpid函数⽤来取得⽬前进程的进程识别码
printf("
PID:
%d\n"
pid);
set_sighandler();
//对信号处理
Ping%s(%s):
%dbytesdatainICMPpackets.\n"
argv[1],inet_ntoa(dest.sin_addr),datalen);
if((setitimer(ITIMER_REAL,&
val_alarm,NULL))==-1)//定时函数bail("
setitimerfails."
recv_reply();
return0;
//发送ping消息voidsend_ping()
structiphdr*ip_hdr;
//iphdr为IP头部结构体
structicmphdr*icmp_hdr;
//icmphdr为ICMP头部结构体
intlen;
intlen1;
icmp_hdr=(structicmphdr*)(sendbuf);
//字符串指针icmp_hdr->
type=ICMP_ECHO;
//初始化ICMP消息类型typeicmp_hdr->
code=0;
//初始化消息代码code
icmp_hdr->
icmp_id=pid;
//把进程标识码初始给icmp_id
icmp_seq=nsent++;
//发送的ICMP消息序号赋值给icmp序号gettimeofday((structtimeval*)icmp_hdr->
icmp_timestamp,NULL);
//获取当前时间
memcpy(icmp_hdr->
data,hello,strlen(hello));
len=ICMP_HSIZE+strlen(hello);
icmp_hdr->
checksum=0;
//初始化
checksum=checksum((u8*)icmp_hdr,len);
//计算校验和
//printf("
Thesendpackchecksumis:
0x%x\n"
icmp_hdr->
checksum);
sendto(sockfd,sendbuf,len,0,(structsockaddr*)&
dest,sizeof(dest));
//经socket传送数据
//接收程序发出的ping命令的应答voidrecv_reply()
intn;
socklen_tlen;
interrno;
n=nrecv=0;
len=sizeof(from);
//发送ping应答消息的主机IP
while(nrecv<
4)
if((n=recvfrom(sockfd,recvbuf,sizeofrecvbuf,0,(structsockaddr*)&
from,&
len))<
{//经socket接收数据,如果正确接收返回接收到的字节数,失败返回0.if(errno==EINTR)//EINTR表⽰信号中断
continue;
bail("
recvfromerror"
gettimeofday(&
recvtime,NULL);
//记录收到应答的时间if(handle_pkt())//接收到错误的ICMP应答信息
nrecv++;
get_statistics(nsent,nrecv);
//统计ping命令的检测结果
//计算校验和
u16checksum(u8*buf,intlen)
u32sum=0;
u16*cbuf;
cbuf=(u16*)buf;
while(len>
1)
sum+=*cbuf++;
len-=2;
if(len)
sum+=*(u8*)cbuf;
sum=(sum>
>
16)+(sum&
0xffff);
sum+=(sum>
16);
return~sum;
//ICMP应答消息处理inthandle_pkt()
structiphdr*ip;
structicmphdr*icmp;
intip_hlen;
u16ip_datalen;
//ip数据长度doublertt;
//往返时间
structtimeval*sendtime;
ip=(structiphdr*)recvbuf;
ip_hlen=ip->
hlen<
<
2;
ip_datalen=ntohs(ip->
tot_len)-ip_hlen;
icmp=(structicmphdr*)(recvbuf+ip_hlen);
u16sum=(u16)checksum((u8*)icmp,ip_datalen);
Therecvpackchecksumis:
sum);
if(sum)//计算校验和
return-1;
if(icmp->
icmp_id!
=pid)return-1;
type!
=ICMP_ECHOREPLY)return-1;
sendtime=(structtimeval*)icmp->
icmp_timestamp;
//发送时间
rtt=((&
recvtime)->
tv_sec-sendtime->
tv_sec)*1000+((&
tv_usec-sendtime->
tv_usec)/1000.0;
//往返时间
//打印结果
%dbytesfrom%s:
icmp_seq=%uttl=%drtt=%.3fms\n"
ip_datalen,//IP数据长度
inet_ntoa(from.sin_addr),//⽬的ip地址
icmp->
icmp_seq,//icmp报⽂序列号ip->
ttl,//⽣存时间
rtt);
//往返时间
//设置信号处理程序voidset_sighandler()
act_alarm.sa_handler=alarm_handler;
if(sigaction(SIGALRM,&
act_alarm,NULL)==-1)//sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。
参数signum指所要捕获信号或忽bail("
SIGALRMhandlersettingfails."
act_int.sa_handler=int_handler;
if(sigaction(SIGINT,&
act_int,NULL)==-1)
bail("
//统计ping命令的检测结果
voidget_statistics(intnsent,intnrecv)
---%spingstatistics---\n"
inet_ntoa(dest.sin_addr));
//将⽹络地址转换成“.”点隔的字符串格式。
%dpacketstransmitted,%dreceived,%0.0f%%"
"
packetloss\n"
nsent,nrecv,1.0*(nsent-nrecv)/nsent*100);
voidbail(constchar*on_what)
fputs(strerror(errno),stderr);
//:
向指定的⽂件写⼊⼀个字符串(不写⼊字符串结束标记符‘\0’)。
成功写⼊⼀个字符串后,⽂件的位置指针会⾃动后移,函
fputs("
:
stderr);
fputs(on_what,stderr);
fputc('
\n'
//送⼀个字符到⼀个流中
//SIGINT(中断信号)处理程序voidint_handler(intsig)
//统计ping命令的检测结果close(sockfd);
//关闭⽹络套接字
//SIGALRM(终⽌进程)处理程序voidalarm_handler(intsigno)
send_ping();
//发送ping消息
程序结果(注意这⾥的PID):
下⾯我们在对我们写的ping程序进⾏抓包分析
这是通过抓包抓到的我们发的ICMP请求包,当然还有ICMP应答报,可以先看看后⾯的id,和seq,后⾯我们会再次提到,⾸先我们对其中⼀个包展开分析:
这⾥显⽰的是IP报⽂字段,可以看到我们前⾯所展⽰的IP报头⾥⾯所包含的东西,⽐较简单,就不⼀⼀分析了。
然后我们在来看看我我们ICMP报⽂:
可以看到,搜下是类型type=8,说明是个ICMP请求,code=0,然后是校验和,接下来是标识符,这⾥怎么有两个呢,其实⼀个是⼤端表
⽰,⼀个是⼩端表⽰的结果,这⾥的标识符是我们的进程ID,可以看我前⾯ping的时候输出了进程ID,然后是序列号,可以对照⼏个包分析,开始序列号(⼤端和⼩端表⽰)是为0,然后⼀次递增,这是我们在程序⾥⾯所设定的。
接下来是时间戳,开始的时候icmp结构⾥⾯我没有放时间戳,这样系统会放在数据位置,我来我单独放了⼀个时间戳结构这样把它和data分开,关于时间戳⼤⼩我也是抓包分析得出来的,是8个字节。
接下来便是我们的数据部分,为了很好的区分数据区域和前⾯的头,特别把数据部分拿出来分析:
可以看到数据部分恰好是从我们⾃⼰定义的数据地⽅开始的,数据前⾯就是时间戳,开始调试的时候,数据总是被覆盖,后来才找出了⾥⾯的时间戳占了8个字节。
所以调整后刚好合适。
当然不同的类型和代码会导致后⾯的结构有些不⼀样,这需要调整。
上⾯的代码中ICMP和IP都是我们⾃⼰定义的。
系统中也有提供相应的结构,我们直接调⽤就可以了,不过必须先查看⾥⾯结构是如何定义的。
在我这⾥,ICMP中的数据是单独定义在外⾯的,不是⼀起放在⾥⾯的,所以,数据部分需要⾃⼰声明⼀个结构,具体名称