☰
ICMP原始套接字实现Tracert路由追踪原理与VC6实战
2026/10/4 10:34:09 网站建设 项目流程

简介:这份Tracert程序设计报告面向计算机网络课程学习者与网络编程初学者,围绕原始套接字编程与ICMP协议展开,帮助读者理解路由跟踪的底层原理并完成课程设计或实验报告撰写。资源包共1个doc文档,约194KB,内容涵盖设计目的与要求、设计说明、系统详细设计、程序源代码及注释等完整章节,结构贴近高校实验报告规范。文档重点讲解Tracert如何借助TTL字段逐跳探测中间路由器,记录各跳IP地址与到达时延,并涉及Ping连通性检测、WinSock2环境初始化、gethostbyname域名解析及itracert.h自定义函数等实现细节,同时给出程序流程图与主要函数说明。已有168人学习,适合需要参考报告框架、梳理ICMP报文机制或调试VC6.0网络程序的读者借鉴。

1. 从一条 Tracert 命令说起:ICMP 原始套接字到底怎么把路由跳数挖出来

在 Windows 命令行敲下tracert www.example.com,屏幕上会一行行蹦出每一跳的 IP 和三次延迟。很多人用了好几年,却从没想过这背后到底发生了什么——为什么它不用 TCP,不用 UDP 端口探测,偏偏靠 ICMP?为什么每一跳恰好发三个包?为什么有的跳显示* * *?如果你正在做「Tracert 程序设计报告」这类课程设计或工程作业,光会敲命令远远不够,你得自己用原始套接字把 ICMP 报文拼出来、发出去、收回来,再解析 TTL 超时报文里的源地址。这篇笔记就按一线实现的顺序,把 ICMP 原始套接字、TTL 递增、超时报文解析、VC6.0 环境下的 Winsock 编程这几件事讲透,让你能真正跑出一个自己的 tracert 程序,而不是抄一份报告交差。适合有 C 语言基础、正在学网络编程、需要交程序设计报告或想搞懂 ICMP 协议的人。

2. 先搞懂 Tracert 的底层逻辑:TTL 递增与 ICMP 超时

2.1 为什么是 ICMP,而不是 TCP/UDP

Tracert 的核心机制其实非常朴素:利用 IP 头里的 TTL(Time To Live)字段。每经过一个路由器,TTL 减 1,减到 0 时路由器丢弃该包,并向源地址回一个 ICMP Time Exceeded(类型 11,代码 0)报文。Tracert 就是靠这个「报错」来发现路径上的每一跳。

那为什么不用 TCP 或 UDP 来探测?因为你要的是「路由器主动告诉你我是谁」,而只有 IP 层协议才会触发 TTL 超时回包。ICMP 本身就是 IP 层的控制协议,天然适合做这件事。用 UDP 发高端口也能触发 ICMP Port Unreachable,但那只在到达目的主机时才回,中间路由器不会理你。所以做 Tracert,ICMP 是正路。

这里有个容易混淆的点:Tracert 发出的探测包本身是 ICMP Echo Request(类型 8),和 ping 用的是同一种报文。区别在于 Tracert 会故意把 TTL 设成 1、2、3……逐跳递增,而 ping 用默认 TTL(通常 128 或 64)。所以你可以理解为:Tracert = 带不同 TTL 的一串 ping + 解析中间路由器回的 Time Exceeded。

2.2 原始套接字为什么必须用管理员权限

要自己构造 ICMP 报文,普通 Socket 不够用,必须用原始套接字(SOCK_RAW)。原始套接字让你能直接操作 IP 头以下的字节,自己填 ICMP 类型、代码、校验和。代价是:操作系统不再帮你封装,校验和要自己算,而且 Windows 下创建原始套接字需要管理员权限,否则WSASocket或socket会直接返回INVALID_SOCKET,错误码WSAEACCES(10013)。

我见过太多人程序跑不起来,第一反应是代码写错了,其实只是没以管理员身份运行。这个坑后面避坑章节还会细说。

2.3 一次完整探测的报文流转

把一次探测拆开看,流程是这样的:

  1. 构造 ICMP Echo Request,类型 8,代码 0,标识符填进程 ID,序列号填当前跳数。
  2. 计算 ICMP 校验和(覆盖整个 ICMP 报文)。
  3. 设置 IP 头的 TTL 为当前跳数(第 1 跳 TTL=1,第 2 跳 TTL=2……)。
  4. 发送到目标地址。
  5. 用recvfrom接收回包,解析 IP 头,看协议字段是不是 1(ICMP)。
  6. 如果是 ICMP 类型 11(超时),取出源 IP,这就是当前跳的路由器地址。
  7. 如果是 ICMP 类型 0(Echo Reply),说明到达目标,结束。
  8. 如果超时没收到,打印*。

这个流程里,第 3 步设置 TTL 是关键。Windows 下用setsockopt的IP_TTL选项就能改,不需要自己拼 IP 头。但如果你要完全控制,也可以开IP_HDRINCL自己填 IP 头,那样更接近协议本质,但复杂度高不少。课程设计里我一般建议先用IP_TTL,把 ICMP 部分做扎实。

提示:TTL 最大值 255,实际公网路径很少超过 30 跳,循环上限设 30 或 64 都行。

3. 用 VC6.0 + Winsock 写出可运行的 Tracert 骨架

3.1 环境准备与工程配置

VC6.0 虽然老,但很多学校的网络编程课还在用。新建一个 Win32 Console Application,空工程,添加一个.cpp文件。关键配置在Project Settings里:Link选项卡的Object/library modules加上ws2_32.lib。不加这个,链接时会报unresolved external symbol,一堆WSAStartup、socket找不到。

代码开头要#include <winsock2.h>和#include <ws2tcpip.h>,并且注意:如果同时包含windows.h,要把winsock2.h放在前面,否则会有一堆重定义错误。这是 VC6.0 时代的经典血泪经验。

3.2 初始化 Winsock 与创建原始套接字

#include <winsock2.h> #include <ws2tcpip.h> #include <stdio.h> #pragma comment(lib, "ws2_32.lib") int main(int argc, char* argv[]) { WSADATA wsaData; // 初始化 Winsock 2.2 if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf("WSAStartup failed: %d\n", WSAGetLastError()); return 1; } // 创建原始套接字,协议指定 IPPROTO_ICMP SOCKET sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (sock == INVALID_SOCKET) { printf("socket failed: %d\n", WSAGetLastError()); WSACleanup(); return 1; } // 设置接收超时,避免 recvfrom 永久阻塞 int timeout = 3000; // 3 秒 setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)); // ... 后续发送与接收逻辑 closesocket(sock); WSACleanup(); return 0; }

这段代码做了三件事:初始化 Winsock、创建原始套接字、设置接收超时。socket的第三个参数必须是IPPROTO_ICMP,不能写 0,否则创建出来的可能是别的协议套接字。SO_RCVTIMEO设 3 秒,是因为网络不通时recvfrom会一直等,程序看起来像卡死。超时后recvfrom返回SOCKET_ERROR,错误码WSAETIMEDOUT,这时打印*即可。

参数说明:timeout单位是毫秒,3000 对应 3 秒。实际公网探测建议 2000 到 5000 之间,局域网可以设 1000。设太短会误判丢包,设太长程序响应慢。

3.3 构造 ICMP Echo Request 与校验和计算

// ICMP 报文结构(简化版) struct ICMPHeader { BYTE type; // 类型:8 请求,0 应答,11 超时 BYTE code; // 代码:0 USHORT checksum; // 校验和 USHORT id; // 标识符 USHORT seq; // 序列号 }; // 计算校验和:16 位反码求和 USHORT checksum(USHORT* buffer, int size) { unsigned long cksum = 0; while (size > 1) { cksum += *buffer++; size -= sizeof(USHORT); } if (size) { cksum += *(UCHAR*)buffer; } cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >> 16); return (USHORT)(~cksum); } // 构造并发送一个 ICMP Echo Request void sendEcho(SOCKET sock, sockaddr_in* dest, int ttl, int seq) { char packet[sizeof(ICMPHeader) + 32]; memset(packet, 0, sizeof(packet)); ICMPHeader* icmp = (ICMPHeader*)packet; icmp->type = 8; // Echo Request icmp->code = 0; icmp->id = (USHORT)GetCurrentProcessId(); // 用进程 ID 做标识 icmp->seq = (USHORT)seq; icmp->checksum = 0; icmp->checksum = checksum((USHORT*)packet, sizeof(packet)); // 设置 TTL setsockopt(sock, IPPROTO_IP, IP_TTL, (char*)&ttl, sizeof(ttl)); sendto(sock, packet, sizeof(packet), 0, (sockaddr*)dest, sizeof(*dest)); }

校验和函数是 ICMP 编程里最容易写错的地方。原理是把报文按 16 位分组求和,进位回卷,最后取反。注意计算前checksum字段必须清零,否则算出来的校验和是错的,对方收到会直接丢弃,你永远收不到回包。id用进程 ID 是为了区分自己发的包和其他进程的 ICMP 包,seq用跳数,方便匹配。

IP_TTL选项每次发送前都要重设,因为它是套接字级别的属性,设一次会一直生效。如果你在第 1 跳设了 TTL=1,第 2 跳忘了改,那第 2 跳的包也会在第一个路由器超时,结果就是每一跳都显示同一个 IP。

3.4 接收并解析 ICMP 超时报文

// 接收回包并解析 void recvReply(SOCKET sock, int seq) { char buf[1024]; sockaddr_in from; int fromLen = sizeof(from); int ret = recvfrom(sock, buf, sizeof(buf), 0, (sockaddr*)&from, &fromLen); if (ret == SOCKET_ERROR) { printf(" *"); return; } // 跳过 IP 头(原始套接字收到的包含 IP 头) int ipHeaderLen = (buf[0] & 0x0f) * 4; ICMPHeader* icmp = (ICMPHeader*)(buf + ipHeaderLen); if (icmp->type == 11) { // Time Exceeded,中间路由器 printf(" %s", inet_ntoa(from.sin_addr)); } else if (icmp->type == 0) { // Echo Reply,到达目标 printf(" %s (目标到达)", inet_ntoa(from.sin_addr)); } else { printf(" 未知 ICMP 类型 %d", icmp->type); } }

这里有个关键细节:原始套接字收到的数据是包含 IP 头的,所以要先算 IP 头长度再定位 ICMP 头。IP 头长度在第一个字节的低 4 位,乘以 4 就是字节数,通常是 20。跳过之后才是 ICMP 报文。from地址就是回包的路由器或目标主机地址,直接inet_ntoa转成点分十进制。

注意icmp->type == 11是超时,type == 0是目标应答。有些系统回的是type == 3(Destination Unreachable),那说明目标不可达,也要处理。课程设计里至少把 0、11、3 三种处理掉,报告才完整。

4. 把跳数循环、超时重试和输出格式串起来

4.1 主循环:TTL 从 1 到 30

// 主探测循环 for (int ttl = 1; ttl <= 30; ttl++) { printf("%2d", ttl); for (int probe = 0; probe < 3; probe++) { sendEcho(sock, &dest, ttl, ttl); recvReply(sock, ttl); } printf("\n"); // 如果已经到达目标,提前结束 if (reached) break; }

外层循环控制 TTL,内层循环发 3 个包,对应 tracert 默认的「三次探测」。每次探测独立发送、独立接收,这样能看出丢包情况。reached标志在收到type == 0时置位,跳出循环。

为什么是 3 次?这是 tracert 的默认行为,给每个跳提供三个延迟样本,方便判断网络稳定性。你也可以改成 1 次或 5 次,但报告里最好说明为什么选 3。

4.2 延迟计算与输出对齐

要算 RTT,得在发送前GetTickCount()记时间,收到回包后再取一次,差值就是毫秒延迟。输出时用printf("%4d ms", rtt)对齐,看起来才像正经工具。

DWORD start = GetTickCount(); sendEcho(sock, &dest, ttl, ttl); // ... recvfrom 之后 DWORD rtt = GetTickCount() - start; printf(" %4d ms", rtt);

GetTickCount精度约 15.6 毫秒,对 tracert 够用。要更高精度可以用QueryPerformanceCounter,但课程设计没必要。

4.3 域名解析与参数处理

用户输入可能是域名,得先用getaddrinfo或gethostbyname转成 IP。VC6.0 时代gethostbyname更常见:

hostent* host = gethostbyname(argv[1]); if (host == NULL) { printf("无法解析域名\n"); return 1; } dest.sin_family = AF_INET; dest.sin_addr = *(in_addr*)host->h_addr_list[0];

gethostbyname返回的h_addr_list是地址列表,取第一个即可。注意它是阻塞的,DNS 慢的时候程序会等,可以加个提示。

5. 避坑与排查:Tracert 程序跑不通的 5 个真实原因

5.1 现象:socket 返回 INVALID_SOCKET,错误码 10013

原因:没有以管理员权限运行。Windows 从 Vista 开始,创建原始套接字需要提升权限。

解决:右键 VC6.0 或编译出的 exe,选「以管理员身份运行」。或者用管理员权限打开 cmd,再从 cmd 启动程序。

5.2 现象:程序发出包但一个回包都收不到

原因:防火墙拦截了 ICMP 回包,或者目标网络禁 ping。也可能是校验和算错,对方直接丢弃。

解决:先关掉 Windows 防火墙测试;用 Wireshark 抓包确认包发出去了没有、校验和字段对不对。如果校验和是 0 或明显不对,检查checksum函数里有没有先清零。

5.3 现象:每一跳显示的都是同一个 IP

原因:IP_TTL没有在每次发送前重设,套接字一直用第一次设的 TTL。

解决:把setsockopt(IP_TTL)放进sendEcho函数里,每次发送都设一遍。

5.4 现象:recvfrom 一直阻塞,程序像死机

原因:没有设SO_RCVTIMEO,或者超时时间设得太长。

解决:创建套接字后立刻设SO_RCVTIMEO,3 秒左右。超时后recvfrom返回SOCKET_ERROR,判断错误码WSAETIMEDOUT就打印*。

5.5 现象:收到的 ICMP 类型是 3 而不是 11 或 0

原因:目标不可达,中间路由器回了 Destination Unreachable。

解决:在解析里加上type == 3的分支,打印「目标不可达」并结束。不要当成未知类型忽略,否则报告里会漏掉一种重要情况。

6. 进阶技巧:用 Wireshark 验证你的 Tracert 程序

写完程序别急着交报告,先用 Wireshark 抓一次包,对照你的输出看。过滤条件写icmp,然后运行你的程序。你应该能看到:

  • 你发出的 Echo Request,TTL 依次是 1、2、3……
  • 中间路由器回的 Time Exceeded,源 IP 和你程序打印的一致
  • 目标主机回的 Echo Reply,类型 0

如果 Wireshark 里看到的 TTL 和你设的不一样,说明setsockopt没生效;如果看到校验和错误,说明校验和函数有问题。这一步能把「玄学」变成「实锤」。

另一个技巧:把每次探测的 RTT 存到数组里,最后算平均延迟和丢包率,输出成表格。报告里有一张这样的表,比干巴巴的代码有说服力得多。

跳数IP 地址探测1探测2探测3平均延迟
1192.168.1.12ms1ms2ms1.7ms
210.0.0.15ms4ms6ms5.0ms
3****超时

最后说个我自己的习惯:每次调 ICMP 程序,我一定先在本机ping 127.0.0.1确认协议栈正常,再tracert一个局域网地址,最后才上公网。这样出问题时能快速定位是代码问题还是网络问题。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询