C实现traceroute(MacOS & Linux系统)

//

#include <stdio.h>

#include <netinet/ip.h>

#include <netinet/ip_icmp.h>

#include <unistd.h>

#include <sys/time.h>

#include <string.h>

#include <stdlib.h>


#include <sys/socket.h>//socket()

#include <arpa/inet.h>

//探测路由结构体:

struct Detect{

    int seq; //当前报文序号

    struct timeval * time; //记录时间

};

//计算时间差函数:

float timediff(struct timeval * recTime,struct timeval * nowTime){

    struct timeval sub = *nowTime;

    

    if ((sub.tv_usec -= recTime->tv_usec) < 0)

    {

        --(sub.tv_sec);

        sub.tv_usec += 1000000;

    }

    sub.tv_sec -= recTime->tv_sec;

    

    return sub.tv_sec * 1000.0 + sub.tv_usec / 1000.0; //转换单位为毫秒

}

//检查和函数:

unsigned short checkSum(unsigned short * icmp,int size){

    unsigned int sum = 0;

    while (size>1) {

        sum = sum + *icmp;

        icmp += 1;          //这里不加2是因为short类型指针每次移动2字节

        size = size - 2;    //16位的方式求和

    }


    if (size == 1) {

        sum = sum + *icmp;

    }

    //加完了如果有进位就,一定是第16位是1,让低位加1即可

    sum = (sum >> 16) + (sum & 0xffff);

    //只有有符号数有反码,无符号数没有反码的概念

    return (unsigned short)~sum;

//    u_int16_t *data = (u

}

//接续icmp超时差错报文函数(成功返回1):

int unpack(char * buf,ssize_t size,char * addr){

    struct ip * ip;

    struct icmp * icmp;

    int ipHLen;

    int icmpLen;

    

    //解析ip报文只为了找到icmp起始位置:

    ip = (struct ip *)buf;

    ipHLen = ip->ip_hl << 2;//因为首部长度以4字节位单位,但计算机以字节为单位

    //找到icmp报文的起始位置:

    icmp = (struct icmp *)(buf + ipHLen);

    //解析icmp报文:

    icmpLen = (int)(size-ipHLen);

    if (icmpLen < 8) {

        printf("ICMP报文长度小于8!\n");

        return 0;

    }

    if(icmp->icmp_type == 11){

//        printf("是超时报文\n");

        struct ip * oldIP = (struct ip *)(buf + ipHLen+8);

        //解析打印:

        printf("from %s: ttl=%d ",addr,oldIP->ip_ttl);

        return 1;

    }else{

//        printf("不是超时报文!\n");

        return 2;

    }

}

//封装icmp报文函数:

void myPackIcmp(char * buf,int sequence){

    struct icmp * icmp = (struct icmp *)buf;

    icmp->icmp_type = ICMP_ECHO;

    icmp->icmp_code = 0;

    icmp->icmp_cksum = 0;

    icmp->icmp_id = getpid();

    icmp->icmp_seq = sequence;

    gettimeofday((struct timeval *)(icmp->icmp_data), 0);

    icmp->icmp_cksum = checkSum((unsigned short *)icmp, sizeof(struct icmp));

}


int main(int argc, const char * argv[]) {

    //数据包发送到哪和从哪收到的数据包的结构体,并清空空间:

    struct sockaddr_in sendAdd;

    struct sockaddr_in recAdd;

    memset(&sendAdd, 0, sizeof(sendAdd));

    memset(&sendAdd, 0, sizeof(recAdd));

    //收到数据包的源地址的长度:

    unsigned int recAddInt = sizeof(struct sockaddr_in);

    char recBuf[128];//接受数据缓冲区

    char sendBuf[128];//发送数据缓冲区

    //清空接收/发送缓冲区的数据,清零:

    memset(recBuf,0,128);

    memset(sendBuf,0,128);

    //socket返回值:

    int sockfd;

    //icmp报文序号:

    int sequence = 0;

    //recfrom()函数的返回值,记录收到的字节个数:

    ssize_t recNum;

    //ip地址(数字形式):unsigned int(4Bytes)

    in_addr_t inadd;

    //记录recvfrom报错次数,超过5次说明到终点:

    int recvFromCount = 0;

    //创建探测结构体:

    struct Detect * detect = (struct Detect *)malloc(sizeof(struct Detect));

    float diffTime;

    struct timeval now;

    //键盘输入缓冲区:

    char input[20];

    char * pInput;//指向input缓冲

    //创建原始套接字:

    if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1) {

        printf("原始套接字创建出错!\n");

        exit(1);//异常退出

    }

    //设置目的IP:

    printf("请输入要Ping的地址(点分十进制):\n");

    scanf("%s",input);

    pInput = input;

    inadd = inet_addr(pInput);//将点分十进制转换成数字

    //填充sendAdd结构体:

    sendAdd.sin_addr.s_addr = inadd;

    sendAdd.sin_family = AF_INET;

    printf("IP: %s \n",inet_ntoa(sendAdd.sin_addr));

    //recvfrom()函数的阻塞时间设置:

    //因为有的路由不回复超时差错报文,如果不设置时间程序就可能卡死:

    struct timeval tv;

    tv.tv_sec = 5;

    tv.tv_usec = 0;

    if(setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(tv)) != 0){

        printf("setsockopt error2!\n");

        exit(1);

    }

    //初始化ttl:

    int ttl = 0;

    while(1) {//循环次:

        //封装ICMP报文:

        myPackIcmp(sendBuf, sequence);

        //设置ttl时间:

        ttl = ttl +1;

        if ((setsockopt(sockfd, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl))) != 0) {

            printf("setsockopt error2!\n");

            exit(1);

        }

        //发送:

        //为了保存当前所发送的报文序号的时间,方便计算时延:

        detect->seq = sequence;

        struct timeval t;

        detect->time = &t;

        gettimeofday(detect->time, 0);//微秒

        //需要将sockaddr_in指针转换成sockaddr

        if ((sendto(sockfd, sendBuf, 64, 0,(struct sockaddr *)&sendAdd, sizeof(sendAdd))) == -1) {

            printf("发送sento函数调用返回-1!\n");

            continue;

        }

        /*

         这里可以不需要嵌套循环!

         */

        while (1) {

            //接收:(需要记录函数返回值,表示收到的字节数)

            if ((recNum = recvfrom(sockfd, recBuf, sizeof(recBuf), 0, (struct sockaddr *)&recAdd, &recAddInt)) == -1) {

                printf("接收recfrom函数调用返回-1!\n");

                recvFromCount += 1;

                break;

            }else{

                //解析:

//                printf("%s\n",inet_ntoa(recAdd.sin_addr));

                gettimeofday(&now, 0);

                int judge = unpack(recBuf,recNum,inet_ntoa(recAdd.sin_addr));

                if (judge == 0) {//ICMP_LEN < 8

                    printf("unpack失败!\n");

                }else if(judge == 1){//REPLY = 11

//                    printf("解码正确\n");

                    //计算rtt时间:

                    struct ip * ip;

                    struct icmp * icmp;

                    int ipHLen;

                    //解析ip报文只为了找到icmp起始位置:

                    ip = (struct ip *)recBuf;

                    ipHLen = ip->ip_hl << 2;//因为首部长度以4字节位单位,但计算机以字节为单位

                    //找到icmp报文的起始位置:

                    icmp = (struct icmp *)(recBuf + ipHLen);

                    

                    diffTime = timediff(detect->time, &now);

                    //解码正确时,ping地址,得到时延(太耗费资源,而且没这个必要):

                    printf("time = %f ms\n",diffTime);

                    

                    recvFromCount = 0;

                    break;

                }else{//REPLY = other ('0',...)

                    printf("*******");

                    printf("解码错误");

                    printf("*******\n");

                }

            }

        }

        sleep(1);//等一秒;

        sequence = sequence+1;//icmp报文序号加一

        //结束判断:

        if (recvFromCount == 5) {

            break;

        }

    }

    printf("结束!\n");

    return 0;

}


猜你喜欢

转载自blog.csdn.net/guozirong123/article/details/78866089