ping程序

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详解卷一

3. linux下判断网络是否连接

原文地址:https://www.cnblogs.com/embedded-linux/p/7072729.html