一步一步实现Linux下Ping命令
Ping命令是ICMP协议的一个使用范例。Ping命令主要是用于测试一台主机和另外一台主机之间的网络连通性。主要的过程就是Ping程序通过发送回显请求报文,然后接收远程主机的回显应答报文,通过分析回显应答报文就可知道两台主机的网络连通性。
ICMP数据报首部如下
| 类型(0或8) | 代码(0) | 校验和 |
| 标识符 | 序号 |
| 选项数据 |
实现ping命令主要就是自己构造ICMP回显请求报文,通过套接字发送到目的主机,然后接收目的主机的ICMP回显应答报文,通过解析回显应答报文就知道是否可以到达远程主机了!
下面来说一下分析一下ICMP报文首部各个字段的具体含义,只有知道了具体的含义才能知道怎么来构造自己的ICMP报文:
ICMP回显请求报文和回显应答报文都属于查询类报文的一种。类型字段指名了该报文是回显请求报文还是回显应答报文:0代表回显应答报文,8代表回显请求报文;代码字段在这两种报文的情况下都是0;校验和是重点,应该无效的报文是不能利用的,所以,通过校验和我们可以获取有效报文再进行报文分析;标识符字段一般的做法都是设置为进程的ID号,这样简洁明了,而且使得我们可以在一台主机上运行多个ping命令实例,不会发生冲突。序号字段从0开始,每发送一次新的会显请求就会增加1.数据选项我们只需注意,回显请求报文和回显应答报文该字段设置成一样即可。
下面看看ICMP在linux下有关数据结构的标示方式:定义可查看
http://freebsd.active-venture.com/FreeBSD-srctree/newsrc/netinet/ip_icmp.h.html
首先看看回显请求和回显应答的命令定义:
#define ICMP_ECHOREPLY 0 /* echo reply */ #define ICMP_ECHO 8 /* echo service */再来看看icmp的数据结构:
/* * Structure of an icmp header. */ struct icmp { u_char icmp_type; /* type of message, see below */ u_char icmp_code; /* type sub code */ u_short icmp_cksum; /* ones complement cksum of struct */ union { u_char ih_pptr; /* ICMP_PARAMPROB */ struct in_addr ih_gwaddr; /* ICMP_REDIRECT */ struct ih_idseq { n_short icd_id; n_short icd_seq; } ih_idseq; int ih_void; /* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */ struct ih_pmtu { n_short ipm_void; n_short ipm_nextmtu; } ih_pmtu; struct ih_rtradv { u_char irt_num_addrs; u_char irt_wpa; u_int16_t irt_lifetime; } ih_rtradv; } icmp_hun; #define icmp_pptr icmp_hun.ih_pptr #define icmp_gwaddr icmp_hun.ih_gwaddr #define icmp_id icmp_hun.ih_idseq.icd_id #define icmp_seq icmp_hun.ih_idseq.icd_seq #define icmp_void icmp_hun.ih_void #define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void #define icmp_nextmtu icmp_hun.ih_pmtu.ipm_nextmtu #define icmp_num_addrs icmp_hun.ih_rtradv.irt_num_addrs #define icmp_wpa icmp_hun.ih_rtradv.irt_wpa #define icmp_lifetime icmp_hun.ih_rtradv.irt_lifetime union { struct id_ts { n_time its_otime; n_time its_rtime; n_time its_ttime; } id_ts; struct id_ip { struct ip idi_ip; /* options and then 64 bits of data */ } id_ip; struct icmp_ra_addr id_radv; u_int32_t id_mask; char id_data[1]; } icmp_dun; #define icmp_otime icmp_dun.id_ts.its_otime #define icmp_rtime icmp_dun.id_ts.its_rtime #define icmp_ttime icmp_dun.id_ts.its_ttime #define icmp_ip icmp_dun.id_ip.idi_ip #define icmp_radv icmp_dun.id_radv #define icmp_mask icmp_dun.id_mask #define icmp_data icmp_dun.id_data };我们可以比较容易的看到类型、代码和校验和这些字段。接下来是一个联合的数据类型,里面的数据项比较多,可以看到序号之类的字段,这样看起来比较晕,型号内核为我们定义了下面的一些宏定义,使得我们只需要关注自己感兴趣的部分,例如:
#define icmp_id icmp_hun.ih_idseq.icd_id 则是ICMP数据报首部的标识符;
#define icmp_seq icmp_hun.ih_idseq.icd_seq 则是我们前面提到的序号字段;
#define icmp_data icmp_dun.id_data 则是数据字段,可设置为发送数据报的时间。
总结:我们仅仅需要设置这几个字段就好。
icmp->icmp_type
icmp->icmp_code
icmp->icmp_cksum
icmp->icmp_id
icmp->icmp_seq
icmp ->icmp_data;