ping程序目的是为了测试另一台主机是否可达。该程序发送一份ICMP回显请求(icmp消息类型0x8,ICMP_ECHO)报文给主机,并等待返回ICMP回显应答(消息类型0x0,ICMP_ECHOREPLY)。
ping -q -w 1 -c 1 www.baidu.com >/dev/null 2>&1 echo $? 0
ping程序还能测出到这台主机的往返时间,以表明该主机离我们“多远”。
用tcpdump抓取一包ping 8.8.8.8的数据:
~$sudo tcpdump -i eth0 icmp -n -v -s0 tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes 17:41:11.487640 IP (tos 0x0, ttl 64, id 48592, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.1.10 > 8.8.8.8: ICMP echo request, id 6914, seq 1, length 64 17:41:11.980277 IP (tos 0x0, ttl 40, id 54159, offset 0, flags [none], proto ICMP (1), length 84) 8.8.8.8 > 192.168.1.10: ICMP echo reply, id 6914, seq 1, length 64
输出的第一行包括目的主机的 I P地址,尽管指定的是它的名字( www.baidu.com)。这说明名字已经经过解析器被转换成 I P地址了。我们将在第 1 4章介绍解析器和 D N S。现在,我们发现,如果敲入 p i n g命令,几秒钟过后会在第 1行打印出 I P地址, D N S就是利用这段时间来确定主机名所对应的 I P地址。
通常,第 1个往返时间值要比其他的大。这是由于目的端的硬件地址不在 A R P高速缓存中的缘故。在发送第一个回显请求之前要发送一个 A R P请求并接收A R P应答,这需要花费几毫秒的时间。
ICMP回显请求和回显应答报文如图:
unix系统在实现ping程序时把ICMP报文中的标识符字段置为发送进程的ID号。这样即使在同一台主机上同时运行多个ping程序,ping程序也可以识别返回的信息。
序列号从0开始,每发送一个新的回显请求就加1。ping程序打印出返回的每个分组的序列号,允许我们查看是否有分组丢失、失序或重复。IP是一种最好的数据报传递服务,因此这三个条件都有可能发生。
ping程序通过在ICMP报文数据中存放发送请求的时间值来计算往返时间。当应答返回时,用当前时间减去存放在ICMP报文中的时间值,即往返时间。
ping程序初版
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <fcntl.h> #include <sys/types.h> #include <stdbool.h> #include <sys/time.h> #include <sys/wait.h> #include <signal.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/ip_icmp.h> #include <netdb.h> #include <arpa/inet.h> #include <errno.h> #define PACKET_SIZE 4096 #define MAX_WAIT_TIME 1 #define ICMP_HEADSIZE 8 struct timeval tvrecv; struct sockaddr_in dest_addr, recv_addr; int sockfd; pid_t pid; char packetsend[PACKET_SIZE]; char packetrecv[PACKET_SIZE]; int seq_no = 0; int err_ret = 0; unsigned short cal_chksum(unsigned short *addr, int len); int pack(int pkt_no, char *packet); int unpack(int cur_seq, char *buf, int len); int send_packet(int pkt_no, char *packet); int recv_packet(int pkt_no, char *packet); void tv_sub(struct timeval *out, struct timeval *in); void close_socket(); void print_hex(const char *buf, int len); void print_hex(const char *buf, int len) { int i = 0; printf("------------------------------ "); for(i = 0; i < len;){ printf("0x%.2X ", (unsigned char)buf[i]); i++; if(i%4 == 0){ printf(" "); } } printf("------------------------------ "); } void alarm_task(int signo) { printf("------------------ping-------%d----------------------- ", seq_no); if(send_packet(seq_no, packetsend)<0){ printf("[NetStatus] error : send_packet "); err_ret = -1; } else { if(recv_packet(seq_no, packetrecv)<0){ printf("[NetStatus] error : recv_packet "); err_ret = -1; } else { seq_no++; alarm(MAX_WAIT_TIME); } } } int main(int argc, char **argv) { struct hostent *host = NULL; struct protoent *protocol = NULL; #ifdef _USE_DNS char hostname[32]; sprintf(hostname, "%s", "www.baidu.com"); bzero(&dest_addr, sizeof(dest_addr)); dest_addr.sin_family = AF_INET; if(host=gethostbyname(hostname) == NULL){ printf("[NetStatus] error : can't get serverhost info! "); return -1; } bcopy((char *)host->h_addr, (char*)&dest_addr.sin_addr, host->h_length); #else dest_addr.sin_addr.s_addr = inet_addr("8.8.8.8"); #endif if((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0){ printf("[NetStatus] error : socket "); return -1; } pid = getpid(); printf("pid=[%d] ", pid); signal(SIGALRM, alarm_task); raise(SIGALRM); while(1){ sleep(1); if(err_ret == -1){ close_socket(); return -1; } } close_socket(); return 0; } int send_packet(int pkt_no, char * packet) { int packetsize; packetsize = pack(pkt_no, packet); if(sendto(sockfd, packet, packetsize, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0){ printf("[NetStatus] error : sendto error "); return -1; } printf("send packet [%d]: ", packetsize); print_hex(packet, packetsize); return 1; } int recv_packet(int pkt_no, char *packet) { int n, fromlen; fd_set rfds; struct timeval tv; int ret = 0; FD_ZERO(&rfds); FD_SET(sockfd, &rfds); fromlen = sizeof(recv_addr); tv.tv_sec = 1; tv.tv_usec = 0; while(1){ ret = select(sockfd+1, &rfds, NULL, NULL, &tv); if(ret > 0){ if(FD_ISSET(sockfd, &rfds)){ if((n = recvfrom(sockfd, packet, PACKET_SIZE, 0, (struct sockaddr *)(&recv_addr), (socklen_t *)&fromlen)) < 0){ if(errno == EINTR) continue; perror("recvform error"); return -1; } } gettimeofday(&tvrecv, NULL); if(unpack(pkt_no, packet, n) < 0){ //continue; return -1; } return 1; } else if(ret == 0){ return 0; } else { return -1; } } } int pack(int pkt_no, char *packet) { int packsize; struct icmp *icmp; struct timeval *tv; icmp = (struct icmp*)packet; icmp->icmp_type = ICMP_ECHO; icmp->icmp_code = 0; icmp->icmp_cksum = 0; icmp->icmp_seq = pkt_no; icmp->icmp_id = pid; packsize = ICMP_HEADSIZE + sizeof(struct timeval); tv = (struct timeval*)icmp->icmp_data; gettimeofday(tv, NULL); icmp->icmp_cksum = cal_chksum((unsigned short*)icmp, packsize); return packsize; } unsigned short cal_chksum(unsigned short *addr, int len) { int nleft = len; int sum = 0; unsigned short *w=addr; unsigned short answer = 0; while(nleft > 1){ sum += *w++; nleft -= 2; } if(nleft == 1){ *(unsigned char*)(&answer)=*(unsigned char *)w; sum += answer; } sum = (sum>>16) + (sum&0xffff); sum += (sum>>16); answer = ~sum; return answer; } int unpack(int cur_seq, char *buf, int len) { int iphdrlen; struct ip *ip; struct icmp *icmp; struct timeval *ptv; double rtt = 0.0; ip = (struct ip *)buf; iphdrlen=ip->ip_hl<<2; icmp = (struct icmp*)(buf + iphdrlen); len -= iphdrlen; if(len < 8){ return -1; } ptv = (struct timeval *)icmp->icmp_data; if((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid) && (icmp->icmp_seq == cur_seq)) { printf("receive packet %d: ", len); print_hex((char *)icmp, len); tv_sub(&tvrecv, ptv); rtt = tvrecv.tv_sec * 1000 + tvrecv.tv_usec/1000.0; printf("count %.3f ms ", rtt); return 0; } else { return -1; } } void tv_sub(struct timeval *out, struct timeval *in) { if((out->tv_usec -= in->tv_usec) < 0){ --out->tv_sec; out->tv_usec += 1000000; } out->tv_sec -= in->tv_sec; } void close_socket() { close(sockfd); sockfd = 0; }
运行:
~$sudo ./a.out pid=[8719] ------------------ping-------0----------------------- send packet [16]: ------------------------------ 0x08 0x00 0x97 0x3E 0x0F 0x22 0x00 0x00 0xE4 0xDE 0x4F 0x59 0x18 0x67 0x05 0x00 ------------------------------ ------------------ping-------1----------------------- send packet [16]: ------------------------------ 0x08 0x00 0xEC 0x38 0x0F 0x22 0x01 0x00 0xE6 0xDE 0x4F 0x59 0xC0 0x6C 0x05 0x00 ------------------------------ receive packet 16: ------------------------------ 0x00 0x00 0xF4 0x38 0x0F 0x22 0x01 0x00 0xE6 0xDE 0x4F 0x59 0xC0 0x6C 0x05 0x00 ------------------------------ count 285.443 ms ------------------ping-------2----------------------- ......
参考:
1. linux环境下C编程指南
2. TCP/IP详解卷一