//
#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;
}