/*****************************************************************************
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 <arpa/inet.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, %ld 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);
}