📄 traceroute.c
字号:
/*****************************************************************************Internet路由跟踪程序 Traceroute 该程序使用UDP包发送一个试探数据报,连续递增改变数据报头的TTL值。TTL每一次“超时”,都会向我们返回一条ICMP消息(传输超时错误或目的端口不可到达错误),我们只要查看给我们发送ICMP消息的主机地址,即可知道该数据报经过了哪些主机(路由器)。 程序中使用了两个套接字,一个是普通的UDP数据报套接字,用IP_TTL选项来改变发送时的TTL值;另一个是原始套接字,用于接收返回来的ICMP消息。******************************************************************************/#include <sys/time.h>#include <sys/socket.h>#include <netinet/in_systm.h>#include <netinet/in.h>#include <netinet/ip.h>#include <netinet/ip_icmp.h>#include <netinet/udp.h>#include <netdb.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#define MAXPACKET 65535 /* IP包的最大大小*/#define UDPPACKETSIZE 36 /* UDP数据报的大小*/#define SRCPORT 23156 /*UDP包的源端口*/#define DSTPORT 58127 /*UDP包的目的端口*//*函数声明*/double deltaT(struct timeval *t1p,struct timeval *t2p); /*计算时间差*/int check_packet(u_char *buf,int cc); /*检查一个IP包是否期望的ICMP数据报*/void send_probe(int sndsock,struct sockaddr_in *whereto,int ttl); /*发送一个探测包*//*接收ICMP消息*/int wait_for_reply(int rcvsock,struct sockaddr_in *from,char *databuf,int buflen); /*主函数*/int main(int argc,char * argv[]){ const int max_ttl=48; /*默认的最大跳数*/ const int nprobes=3; /*默认的每跳探测次数*/ //处理命令行,合法的命令行格式为: tracert 主机名或主机IP地址*/ if(argc!=2) { fprintf(stderr,"Usage: %s host\r\n",argv[0]); exit(-1); } struct hostent *host; /*主机名结构指针*/ struct sockaddr_in haddr; /*远程主机地址结构*/ struct sockaddr_in loc_addr; /*本机地址结构,用于绑定UDP服务于指定的端口*/ bzero(&haddr,sizeof(haddr)); /*填充目的主机地址结构*/ haddr.sin_family=AF_INET; haddr.sin_addr.s_addr=inet_addr(argv[1]); haddr.sin_port=htons(DSTPORT); /*如果是主机名,则查询DNS解析*/ if(haddr.sin_addr.s_addr==INADDR_NONE){ if(NULL==(host=gethostbyname(argv[1]))) { fprintf(stderr,"unknown host %s\r\n",argv[1]); exit(-1); } memcpy(&haddr.sin_addr,host->h_addr,host->h_length); } /*填充本机地址结构*/ loc_addr.sin_family=AF_INET; loc_addr.sin_addr.s_addr=htonl(INADDR_ANY); loc_addr.sin_port=htons(SRCPORT); int sndsock,rcvsock; /*创建UDP套接字*/ if ((sndsock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { fprintf(stderr,"traceroute: udp socket\r\n"); exit(-1); } /*创建RAW套接字,套接字的类型为IPPROTO_ICMP*/ if ((rcvsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) { fprintf(stderr,"traceroute: raw socket\r\n"); exit(-1); } /*绑定UDP套接字于指定的端口*/ if(bind(sndsock,(struct sockaddr*)&loc_addr,sizeof(loc_addr))) { fprintf(stderr,"bind error\r\n"); exit(-1); } fprintf(stdout, "traceroute to %s (%s)", argv[1],inet_ntoa(haddr.sin_addr)); fprintf(stdout, ", %d hops max, %d byte packets\r\n", max_ttl, UDPPACKETSIZE+sizeof(struct ip)+sizeof(struct udphdr)); char databuf[MAXPACKET]; /*接收ICMP数据报的缓冲区*/ struct sockaddr_in from; /*远程主机地址结构*/ int ttl; //循环改变发送的UDP数据报的TTL值,发送探测数据包*/ for (ttl = 1; ttl <= max_ttl; ++ttl) { u_long lastaddr = 0; /*记录上一个接收到的数据包的源地址*/ int got_there = 0; /*记录是否到达了目的主机*/ printf("%2d ", ttl); fflush(stdout); int probe; /*每一跳(TTL值)循环发送nprobes个数据包*/ for (probe = 0; probe < nprobes; ++probe) { int cc=0; struct timeval t1, t2; /*记录发送和接收的时间*/ struct timezone tz; struct ip *ip; gettimeofday(&t1, &tz); /*记录发送时间*/ send_probe(sndsock,&haddr,ttl); /*发送一个UDP数据包*/ /*在指定的时间内等待回复的ICMP包,直到超时*/ while (cc = wait_for_reply(rcvsock, &from,databuf,sizeof(databuf))) { gettimeofday(&t2, &tz); /*记录接收时间*/ if (check_packet(databuf, cc)) { /*检查是否期待的ICMP数据报*/ /*判断是否是上一跳主机返回的数据包,不是的话输出其IP地址*/ if (from.sin_addr.s_addr != lastaddr) { printf("%s ",inet_ntoa(from.sin_addr)); lastaddr = from.sin_addr.s_addr; } /*计算发送和接收数据包之间的间隔,并显示出来*/ printf(" %g ms ", deltaT(&t1, &t2)); /*判断是否到达最终的目的地,是 的话做出标记*/ if(from.sin_addr.s_addr==haddr.sin_addr.s_addr) got_there++; break; } } if (cc == 0) /*cc等于零意味着等待超时,该跳主机没有回应*/ printf(" * "); } printf("\r\n"); /*如果达到目的地,则退出循环*/ if (got_there) break; } return 0;}/***********************************************************************************函数: 等待ICMP消息rcvsock: 接收消息的套接字句柄from: 远程主机地址结构的地址databuf: 接收消息的缓冲区首地址buflen: 消息缓冲区的长度***********************************************************************************/ int wait_for_reply(int rcvsock,struct sockaddr_in *from,char *databuf,int buflen){ const int waittime=4; /*默认超时时间为 4 秒*/ int cc = 0; int fromlen = sizeof (*from); /*套接字IO参数*/ fd_set fds; /* 套接字I/O集合*/ FD_ZERO(&fds); FD_SET(rcvsock, &fds); /*把rcvsock加入到集合fds中*/ /*(超时)时间结构*/ struct timeval wait; wait.tv_sec = waittime; wait.tv_usec = 0; /*选择一个集合fds,并查看该集合中的套接字是否存在待决的I/O操作(我们这里是读取操作)*/ /*默认等待时间为4秒*/ if (select(rcvsock+1, &fds, (fd_set *)0, (fd_set *)0, &wait) > 0) { /*有数据可读,读取到指定的缓冲区中*/ cc=recvfrom(rcvsock, databuf, buflen, 0, (struct sockaddr *)from, &fromlen); } return(cc);}/**********************************************************************************函数: 向指定的地址发送一个UDP数据报sndsock: 发送数据报套接字whereto: 目的主机地址结构指针ttl: 发送数据包的TTL值**********************************************************************************/void send_probe(int sndsock,struct sockaddr_in *whereto,int ttl){ char databuf[UDPPACKETSIZE]; /*发送数据包缓冲区(数据部分)*/ bzero(databuf,sizeof(databuf)); /*设置发送套接字的选项(IPPROTO_IP级,IP_TTL类型),更改IP包头中的TTL为指定值*/ setsockopt(sndsock,IPPROTO_IP,IP_TTL,(char *)&ttl,sizeof(ttl)); /*发送数据包(携带UDPPACKETSIZE个字节的零)*/ int n = sendto(sndsock, databuf, sizeof(databuf), 0,(struct sockaddr *)whereto, sizeof(struct sockaddr)); if(n!=UDPPACKETSIZE) { fprintf(stderr,"Error in sendto\r\n"); }}/**********************************************************************************函数: 检查一个返回的IP包是否为期待的ICMP数据报(TTL超时或者目的端口不可到达)buf: 数据缓冲区首地址cc: 缓冲区中数据大小**********************************************************************************/int check_packet(u_char *buf,int cc){ /*处理IP包头*/ struct ip *ip= (struct ip *) buf; /*计算IP头大小*/ int hlen = ip->ip_hl << 2; /*从大小来判断是否是一个ICMP包*/ if (cc < hlen + ICMP_MINLEN) { return 0; } cc -= hlen; /*处理ICMP头部*/ struct icmp *icp= (struct icmp *)(buf + hlen); u_char type=icp->icmp_type; /*ICMP消息类型*/ u_char code=icp->icmp_code; /*ICMP消息代码*/ /*期待的ICMP消息只有两种:传输超时(TTL变为0),目的端口不可到达(已经到了目的主机)*/ if(type==ICMP_TIMXCEED || type==ICMP_UNREACH) { struct ip *hip=&icp->icmp_ip; hlen=hip->ip_hl<<2; /*ICMP数据报会发回出错的数据报的IP头部和该IP数据报的头8个字节 我们检查该数据报的目的端口和源端口,看是否是我们发送的测试数据报*/ struct udphdr *udp=(struct udphdr *)((u_char *)hip+hlen); if(hip->ip_p==IPPROTO_UDP && udp->dest==htons(DSTPORT) && udp->source==htons(SRCPORT)) /*if(hip->ip_p==IPPROTO_UDP && udp->uh_dport==htons(DSTPORT) && udp->uh_sport==htons(SRCPORT))*/ return 1; } return 0;}/**********************************************************************************函数: 计算两个timeval结构表示的时间差值**********************************************************************************/double deltaT(struct timeval *t1p,struct timeval *t2p){ double dt; dt = (double)(t2p->tv_sec - t1p->tv_sec) * 1000.0 + (double)(t2p->tv_usec - t1p->tv_usec) / 1000.0; return (dt);}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -