一、Tracert介紹
Tracert是一個網絡診斷命令,可以幫助用戶跟蹤數據包在互聯網上的路由路徑。它使用時間戳來確定當數據包通過路由器時經過的時間,從而計算出每個路由器的故障點或延遲。
二、Tracert實現原理
Tracert的實現原理主要是利用網際控制消息協議(ICMP)。當一個路由器收到一個ICMP超時信息時,它會將該信息返回給發送者。通過多次向目標服務器發送ICMP報文並等待返回,Tracert可以確定從發送者到目標服務器之間的路由路徑。
具體來說,Tracert使用了一個TTL(time to live)的變量來實現探測路由器路徑的功能。TTL是一個用於控制數據包在傳輸過程中的壽命的變量。每經過一個路由器,這個變量的值都會減小1。當TTL的值減為0時,路由器會丟棄該數據包並返回一個ICMP超時消息,Tracert就會收到這個消息,因此Tracert可以知道這個路由器的IP地址。
Tracert還利用了UDP協議和ICMP差錯消息進行探測,這樣可以更好地避免網絡防火牆的干擾。
三、Tracert操作步驟
Tracert的操作非常簡單,用戶只需在命令行窗口中輸入「tracert 目標主機名稱/目標主機地址」即可開始跟蹤數據包的路由路徑。Tracert通常會發送三次ICMP報文以便更準確地識別路由路徑。
Tracert的具體步驟如下:
1.向目標地址發送一個TTL為1的ICMP報文。
sendto(sockfd, packet, ICMP_PACKET_SIZE, 0, (struct sockaddr *)&dest_addr,sizeof(dest_addr));
2.等待路由器返回一個ICMP超時消息,記錄路由器的IP地址。
if(recv(sockfd,temp_buf,BUF_SIZE,0)>0)//接收ICMP超時信息 { gettimeofday(&end_time, NULL);//記錄收到應答的時間 char str[INET_ADDRSTRLEN];//用於保存路由器IP地址的char數組 inet_ntop(AF_INET,&from.sin_addr,str,INET_ADDRSTRLEN);//將路由器地址轉換為ip地址格式 printf("%d\t%s\n",ttl,str);//輸出每層路由器的地址 if(strcmp(inet_ntoa(from.sin_addr),inet_ntoa(dest_addr.sin_addr))==0)//判斷是否到達目標ip地址 { finish_flag = 1;//設置結束標誌 printf("trace finish!\n"); break; } memset(temp_buf,0,sizeof(temp_buf));//清空temp_buf數組 }
3.向目標地址發送一個TTL為2的ICMP報文,並等待返回信息。
sendto(sockfd, packet, ICMP_PACKET_SIZE, 0, (struct sockaddr *)&dest_addr,sizeof(dest_addr));
4.依次遞增TTL,重複以上步驟直到目標地址被找到或者跟蹤次數超過限制。
四、Tracert注意事項
在使用Tracert時需要注意以下幾點:
1.Tracert功能依賴於ICMP協議,請確保網絡中沒有屏蔽ICMP協議的防火牆或路由器。
2.在Tracert過程中,可能會遇到延遲、丟包等問題,用戶需要結合實際情況判斷網絡狀況。
3.由於TTL控制的是數據包的路由路徑,而不是數據包傳輸的時間,因此TTL值的大小並不能反映出網絡延遲的真實情況。
五、代碼示例
下面是使用C語言實現Tracert的代碼示例,僅供參考。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <netdb.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/time.h> #include <netinet/ip_icmp.h> #define BUF_SIZE 1024 #define ICMP_PACKET_SIZE sizeof(struct icmp) #define SEND_NUM 3 #define MAX_TTL 64 char *host_name; struct sockaddr_in dest_addr; int sockfd; pid_t pid; struct timeval start_time, end_time; void fails(const char *msg)//錯誤提示函數 { printf("%s. errno=%d\n", msg, errno); exit(1); } void intHandler(int dummy)//信號處理函數 { close(sockfd); printf("\ntraceroute finish!\n"); exit(0); } unsigned short chksum(unsigned short *addr, int len) //校驗和函數 { int sum = 0; unsigned short answer = 0; unsigned short *w = addr; int nleft = len; while (nleft > 1) { sum += *w++; nleft -= 2; } if (nleft == 1) { *(unsigned char *)(&answer) = *(unsigned char *)w; sum += answer; } sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); answer = ~sum; return answer; } void send_packet(int ttl) //發送icmp包函數 { char packet[BUF_SIZE]; struct icmp *icp = (struct icmp *)packet; icp->icmp_type = ICMP_ECHO; icp->icmp_code = 0; icp->icmp_id = pid; icp->icmp_seq = ttl; memset(icp->icmp_data, 0xa5, ICMP_PACKET_SIZE); //填充icmp_data gettimeofday((struct timeval*)icp->icmp_data, NULL); icp->icmp_cksum = chksum((unsigned short *)icp, ICMP_PACKET_SIZE); setsockopt(sockfd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); sendto(sockfd, packet, ICMP_PACKET_SIZE, 0, (struct sockaddr *)&dest_addr,sizeof(dest_addr)); gettimeofday(&start_time, NULL); } void recv_packet(int ttl) //接收icmp包函數 { char temp_buf[BUF_SIZE]; memset(temp_buf, 0, sizeof(temp_buf)); struct ip *ip = (struct ip *)temp_buf; int read_len = read(sockfd, temp_buf, BUF_SIZE); //接收icmp數據包 if (read_len < 0) fails("read icmp packet error"); if (read_len < sizeof(struct ip)) return; struct icmp *icmp = (struct icmp *)(ip + 1); int icmp_len = read_len - sizeof(struct ip); if (icmp_len icmp_type == ICMP_TIMXCEED && icmp->icmp_code == ICMP_TIMXCEED_INTRANS) //判斷icmp包類型 { int iplen = ip->ip_hl <ip_hl <icmp_id == pid && icmp_inner->icmp_seq == ttl) //判斷是否是本程序發出的icmp包的應答 { gettimeofday(&end_time, NULL); double time_used = (double)(end_time.tv_sec - start_time.tv_sec) * 1000.0 + (double)(end_time.tv_usec - start_time.tv_usec) / 1000.0; char str[INET_ADDRSTRLEN]; inet_ntop(AF_INET,&ip->ip_src,str,INET_ADDRSTRLEN); printf("%d\t%1.1fms\t%s\n", ttl, time_used, str); } } else if (icmp->icmp_type == ICMP_ECHOREPLY && icmp->icmp_id == pid) //判斷icmp包類型 { gettimeofday(&end_time, NULL); double time_used = (double)(end_time.tv_sec - start_time.tv_sec) * 1000.0 + (double)(end_time.tv_usec - start_time.tv_usec) / 1000.0; char str[INET_ADDRSTRLEN]; inet_ntop(AF_INET,&ip->ip_src,str,INET_ADDRSTRLEN); printf("%d\t%1.1fms\t%s\n", ttl, time_used, str); } } int main(int argc, char *argv[]) { if (argc < 2) fails("usage: ./traceroute "); signal(SIGINT, intHandler); //註冊信號處理函數 pid = getpid(); //獲取當前進程的pid host_name = argv[1]; memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.sin_family = AF_INET; struct hostent *host_entity = gethostbyname(host_name); //獲取主機信息 if (host_entity == NULL) fails("gethostbyname error"); dest_addr.sin_addr = *(struct in_addr *)host_entity->h_addr; char ip_str[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &dest_addr.sin_addr, ip_str, INET_ADDRSTRLEN); printf("traceroute to %s (%s), %d hops max\n", host_name, ip_str, MAX_TTL); sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); //創建icmp套接字 if (sockfd < 0) fails("create icmp socket error"); int ttl = 1; while (1) { setuid(getuid()); //為了使用raw socket而必須要有root權限,調用setuid( )函數改變進程的EUID send_packet(ttl); fd_set descriptors; //監聽套接字事件 FD_ZERO(&descriptors); FD_SET(sockfd, &descriptors); struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; select(sockfd + 1, &descriptors, NULL, NULL, &tv); recv_packet(ttl); if (ttl++ == MAX_TTL) //判斷是否超過ttl最大值 break; sleep(1); //延遲一秒 } close(sockfd); return 0; }
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/190677.html