# ping **Repository Path**: zconf/ping ## Basic Information - **Project Name**: ping - **Description**: ping implementation. - **Primary Language**: C - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2019-05-05 - **Last Updated**: 2021-01-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ping命令的实现 ## 1. 前提知识 ### 1.1 效果及原理 #### 1.1.1 效果 Linux自带ping命令的运行示例如下: ```bash ➤ ping baidu.com -c 3 7:28:25 PING baidu.com (220.181.57.216) 56(84) bytes of data. 64 bytes from 220.181.57.216: icmp_seq=1 ttl=55 time=66.7 ms 64 bytes from 220.181.57.216: icmp_seq=2 ttl=55 time=60.0 ms 64 bytes from 220.181.57.216: icmp_seq=3 ttl=55 time=73.3 ms --- baidu.com ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2000ms rtt min/avg/max/mdev = 60.053/66.740/73.380/5.449 ms ``` 包含的信息有: 1. 目标ip 2. 每个包的往返时间, 即RTT 3. 丢包率 #### 1.1.2 原理 ping命令使用的是ICMP协议, 即(Internet控制报文协议), 这是一个**网络层**协议. 在运行过程中, ping向目标主机发送一系列ICMP报文, 记录每一个ICMP报文的发送时间, 然后等待响应报文, 计算RTT, 丢包率等信息. ### 1.2 报文结构 ### 1.2.1 ip报文结构 在`netinet/ip.h`中可以发现报文结构, 其中有两个结构体`iphdr`和`ip`, 这两个代表的其实是同一种结构, 不过`iphdr`是Linux特有的, [参考这里](https://stackoverflow.com/questions/42840636/difference-between-struct-ip-and-struct-iphdr), 一般来说使用`struct ip`就行. ip的报文头: ![ip header](./ip.jpeg) ```c struct ip { #if __BYTE_ORDER == __LITTLE_ENDIAN unsigned int ip_hl:4; /* header length */ unsigned int ip_v:4; /* version */ #endif #if __BYTE_ORDER == __BIG_ENDIAN unsigned int ip_v:4; /* version */ unsigned int ip_hl:4; /* header length */ #endif u_int8_t ip_tos; /* type of service */ u_short ip_len; /* total length */ u_short ip_id; /* identification */ u_short ip_off; /* fragment offset field */ #define IP_RF 0x8000 /* reserved fragment flag */ #define IP_DF 0x4000 /* dont fragment flag */ #define IP_MF 0x2000 /* more fragments flag */ #define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ u_int8_t ip_ttl; /* time to live */ u_int8_t ip_p; /* protocol 1:icmp, 6:tcp, 17:udp */ u_short ip_sum; /* checksum */ struct in_addr ip_src, ip_dst; /* source and dest address */ } ``` 这里注意一下首部长度的表示方法, `ip_hl`只有四位, 如果单位是byte的话, 就只能表示15byte了, 而实际上ip header的最小长度是20byte. 所以`ip_hl`的单位不是byte, 而是32bit. 所以如果`ip_hl`中填写的是5, 那么表示ip header的大小是`5 x 32 bit = 20 byte`. ### 1.2.2 icmp报文结构 ![icmp](./icmp.jpeg) ```c struct icmp { u_int8_t icmp_type; /* type of message, see below */ u_int8_t icmp_code; /* type sub code */ u_int16_t icmp_cksum; /* ones complement checksum of struct */ /* 下面就是icmp的类型相关数据 */ union +-- 24 lines: {---------------------------------------------------------- #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 +-- 16 lines: {---------------------------------------------------------- #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 }; ``` 对于我们要实现的ping命令, icmp的相关数据应该为: ```c icmp_type=ICMP_ECHO; /* 8 */ icmp_code=0; ``` ### 1.2.3 实例分析 下面对ping命令抓包, 分析结构, 帮助理解ip和icmp的报文结构: ```shell $ sudo tcpdump -nni wlp2s0 icmp -X ``` 然后运行`ping baidu.com -c 1`, 出现如下抓到的内容 ``` ➤ sudo tcpdump -nni wlp2s0 icmp -X 10:08:37 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on wlp2s0, link-type EN10MB (Ethernet), capture size 262144 bytes 10:08:43.142340 IP 113.54.222.28 > 220.181.57.216: ICMP echo request, id 16387, seq 1, length 64 0x0000: 4500 0054 66c1 4000 4001 6e07 7136 de1c E..Tf.@.@.n.q6.. 0x0010: dcb5 39d8 0800 955a 4003 0001 ab45 ce5c ..9....Z@....E.\ 0x0020: 0000 0000 e82b 0200 0000 0000 1011 1213 .....+.......... 0x0030: 1415 1617 1819 1a1b 1c1d 1e1f 2021 2223 .............!"# 0x0040: 2425 2627 2829 2a2b 2c2d 2e2f 3031 3233 $%&'()*+,-./0123 0x0050: 3435 3637 4567 10:08:43.509258 IP 220.181.57.216 > 113.54.222.28: ICMP echo reply, id 16387, seq 1, length 64 0x0000: 4500 0054 66c1 4000 2f01 7f07 dcb5 39d8 E..Tf.@./.....9. 0x0010: 7136 de1c 0000 9d5a 4003 0001 ab45 ce5c q6.....Z@....E.\ 0x0020: 0000 0000 e82b 0200 0000 0000 1011 1213 .....+.......... 0x0030: 1415 1617 1819 1a1b 1c1d 1e1f 2021 2223 .............!"# 0x0040: 2425 2627 2829 2a2b 2c2d 2e2f 3031 3233 $%&'()*+,-./0123 0x0050: 3435 3637 4567 ``` 下面来分析一下本地发送到baidu.com的icmp报文 下面是分析的结果: ![dump](./tcpdump.png) ### 1.3 相关API 由于ICMP工作在网络层, 所以我们不能使用**SOCK_STREAM**或者**SOCK_DGRAM**类型套接字, 因为他们都是传输层的东西, 而要使用**SOCK_RAW**参数来创建套接字. 同时协议号也不能直接填个0, 而要查询得到. ```c socket(AF_INET, SOCK_RAW, ); ``` 发送的时候使用sendto, 接受使用recvfrom, 就像UDP那样 --- 参考: [用C语言实现ping程序功能](https://www.ibm.com/developerworks/cn/linux/network/ping/index.html) [ICMP-RFC](https://tools.ietf.org/html/rfc792) http://www.cnblogs.com/scrat/archive/2012/08/02/2620163.html