Tracert原理詳解

一、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-hant/n/190677.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
小藍的頭像小藍
上一篇 2024-11-30 09:06
下一篇 2024-11-30 09:06

相關推薦

  • Harris角點檢測算法原理與實現

    本文將從多個方面對Harris角點檢測算法進行詳細的闡述,包括算法原理、實現步驟、代碼實現等。 一、Harris角點檢測算法原理 Harris角點檢測算法是一種經典的計算機視覺算法…

    編程 2025-04-29
  • 瘦臉算法 Python 原理與實現

    本文將從多個方面詳細闡述瘦臉算法 Python 實現的原理和方法,包括該算法的意義、流程、代碼實現、優化等內容。 一、算法意義 隨着科技的發展,瘦臉算法已經成為了人們修圖中不可缺少…

    編程 2025-04-29
  • 神經網絡BP算法原理

    本文將從多個方面對神經網絡BP算法原理進行詳細闡述,並給出完整的代碼示例。 一、BP算法簡介 BP算法是一種常用的神經網絡訓練算法,其全稱為反向傳播算法。BP算法的基本思想是通過正…

    編程 2025-04-29
  • GloVe詞向量:從原理到應用

    本文將從多個方面對GloVe詞向量進行詳細的闡述,包括其原理、優缺點、應用以及代碼實現。如果你對詞向量感興趣,那麼這篇文章將會是一次很好的學習體驗。 一、原理 GloVe(Glob…

    編程 2025-04-27
  • 編譯原理語法分析思維導圖

    本文將從以下幾個方面詳細闡述編譯原理語法分析思維導圖: 一、語法分析介紹 1.1 語法分析的定義 語法分析是編譯器中將輸入的字符流轉換成抽象語法樹的一個過程。該過程的目的是確保輸入…

    編程 2025-04-27
  • 神經網絡代碼詳解

    神經網絡作為一種人工智能技術,被廣泛應用於語音識別、圖像識別、自然語言處理等領域。而神經網絡的模型編寫,離不開代碼。本文將從多個方面詳細闡述神經網絡模型編寫的代碼技術。 一、神經網…

    編程 2025-04-25
  • Linux sync詳解

    一、sync概述 sync是Linux中一個非常重要的命令,它可以將文件系統緩存中的內容,強制寫入磁盤中。在執行sync之前,所有的文件系統更新將不會立即寫入磁盤,而是先緩存在內存…

    編程 2025-04-25
  • Linux修改文件名命令詳解

    在Linux系統中,修改文件名是一個很常見的操作。Linux提供了多種方式來修改文件名,這篇文章將介紹Linux修改文件名的詳細操作。 一、mv命令 mv命令是Linux下的常用命…

    編程 2025-04-25
  • git config user.name的詳解

    一、為什麼要使用git config user.name? git是一個非常流行的分布式版本控制系統,很多程序員都會用到它。在使用git commit提交代碼時,需要記錄commi…

    編程 2025-04-25
  • Java BigDecimal 精度詳解

    一、基礎概念 Java BigDecimal 是一個用於高精度計算的類。普通的 double 或 float 類型只能精確表示有限的數字,而對於需要高精度計算的場景,BigDeci…

    編程 2025-04-25

發表回復

登錄後才能評論