C语言实现ping命令(二)

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/time.h>
#include<arpa/inet.h>
#include<netdb.h>

#define ICMP_SIZE (sizeof(struct icmp))
#define ICMP_ECHO 0
#define ICMP_ECHOREPLY 0
#define BUF_SIZE 1024
#define NUM 5    //报文发送次数

#define UCHAR unsigned char 
#define USHORT unsigned short
#define UINT unsigned int 


struct icmp
{
    UCHAR   type;                //类型
    UCHAR   code;                //代码
    USHORT  checksum;            //校验和
    USHORT  id;                    //序列号
    USHORT  sequence;            //序号
    struct timeval timestamp;    //时间戳
};


//ip首部数据结构
struct ip
{
    //主机字节序判断
    //#if __BYTE_ORDER == __LITTLE_ENDINA
    #if __BYTE_ORDER == __LITTLE_ENDIAN
    UCHAR   hlen:4;            //首部长度
    UCHAR   version:4;        //版本
    #endif

    #if __BYTE_ORDER == __BIG_ENDIAN
    UCHAR   version:4;
    UCHAR   hlen:4;
    #endif

    UCHAR   tos;        //服务类型
    USHORT  len;        //总长度
    USHORT  id;            //标识符
    USHORT  offset;        //标志和片偏移
    UCHAR   ttl;        //生存时间
    UCHAR   protocol;    //协议
    USHORT  checksum;    //校验和
    struct in_addr  ipsrc;    //32位源ip地址
    struct in_addr  ipdst;    //32位目的ip地址
};

char buf[BUF_SIZE] = {0};

USHORT checkSum(USHORT *, int);        //计算校验和
float timediff(struct timeval *, struct timeval *);    //计算时间差
void pack(struct icmp *, int);        //封装一个ICMP报文
int unpack(char *, int , char *);    //对接收的IP保温进行解包

int main(int argc, char *argv[])
{
    struct hostent *host;
    struct icmp sendicmp;
    struct sockaddr_in from;
    struct sockaddr_in to;
    int fromlen = 0;
    int sockfd;
    int nsend = 0;
    int nreceived = 0;
    int i, n;
    in_addr_t inaddr;

    memset(&from, 0, sizeof(struct sockaddr_in));
    memset(&to, 0, sizeof(struct sockaddr_in));

    if (argc < 2)
    {
        printf("use : %s hostname/IP address \n", argv[0]);
        exit(1);
    }

    //生成原始套接字
    if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1)
    {
        printf("socket() error\n");
        exit(1);
    }

    //设置目的地址信息
    to.sin_family = AF_INET;

    //判断域名还是ip地址(好像没实现)
    if (inaddr = inet_addr(argv[1]) == INADDR_NONE)
    {
        //域名
        if ((host = gethostbyname(argv[1])) == NULL)
        {
            printf("gethostbyname() error \n");
            exit(1);
        }
        to.sin_addr = *(struct in_addr *)host->h_addr_list[0];
    }
    else
    {
        //ip地址
        to.sin_addr.s_addr = inaddr;
    }

    //输出域名ip地址信息
    printf("ping %s (%s) : %d bytes of data.\n", argv[1], inet_ntoa(to.sin_addr),
            (int)ICMP_SIZE);

    //循环发送接收报文
    for (i = 0; i < NUM; i++)
    {
        nsend++;
        memset(&sendicmp, 0, ICMP_SIZE);
        pack(&sendicmp, nsend);

        if (sendto(sockfd, &sendicmp, ICMP_SIZE, 0, (struct sockaddr*)&to, sizeof(to)) == -1)
        {
            printf("sendto() error\n");
            continue;
        }

        if ((n = recvfrom(sockfd, buf, BUF_SIZE, 0, (struct sockaddr *) &from, &fromlen)) < 0)
        {
            printf("recvfrom() error\n");
            continue;
        }

        nreceived++;

        if (unpack(buf, n, inet_ntoa(from.sin_addr)) == -1)
        {
            printf("unpack() error \n");
        }

        sleep(1);
    }

    printf("--- %s ping statistics ---\n", argv[1]);
    printf("%d packets transmitted, %d received, %%%d packet loss\n", nsend, nreceived,
            (nsend - nreceived) / nsend * 100);
        
    return 0;    
}


USHORT checkSum(USHORT *addr, int len)
{
    UINT sum = 0;
    while(len > 1)
    {
        sum += *addr++;
        len -= 2;
    }
    
    //处理剩下的一个字节
    if (len == 1)
    {
        sum += *(UCHAR *)addr;
    }

    //将32位的高16位和低16位相加
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);

    return (USHORT) ~sum;
}
float timediff(struct timeval *begin, struct timeval *end)
{
    int n;
    n = (end->tv_sec - begin->tv_sec) * 100000
        + (end->tv_usec - begin->tv_usec);

    //转为毫秒返回
    return (float) (n / 1000); 
}
void pack(struct icmp *icmp, int sequence)
{
    icmp->type = ICMP_ECHO;
    icmp->code = 0;
    icmp->checksum = 0;
    icmp->id = getpid();
    icmp->sequence = sequence;
    gettimeofday(&icmp->timestamp, 0);
    icmp->checksum = checkSum((USHORT *)icmp, ICMP_SIZE);
}
int unpack(char *buf, int len, char *addr)
{
    int i, ipheadlen;
    struct ip * ip;
    struct icmp * icmp;
    float rtt;
    struct timeval end;

    ip = (struct ip *) buf;
    
    //计算ip首部长度,即ip首部的长度标识乘4
    ipheadlen = ip->hlen << 2;

    //越过ip首部,指向icmp报文
    icmp = (struct icmp *)(buf + ipheadlen);
    
    //icmp报文总长度
    len -= ipheadlen;

    if (len < 8)
    {
        printf("ICMP packets\‘s length is less than 8 \n");
        return -1;
    }

    if (icmp->type != ICMP_ECHOREPLY ||
        icmp->id != getpid())
        {
            printf("ICMP packets are not send by us \n");
            return -1;
        }

    gettimeofday(&end, 0);
    rtt = timediff(&icmp->timestamp, &end);
    printf("%d bytes form %s : icmp_seq=%u ttl=%d rtt=%fms \n", 
            len, addr, icmp->sequence, ip->ttl, rtt);

    return 0;
}

相关推荐