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/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

发表回复

登录后才能评论