news 2026/9/30 5:07:03

从零实现Tracert:原始套接字与ICMP路由跟踪详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零实现Tracert:原始套接字与ICMP路由跟踪详解

简介:一份以计算机网络课程设计为背景的Tracert程序设计报告,面向学习原始套接字编程、ICMP协议与路由跟踪原理的学生或开发人员。报告从设计目的出发,逐步讲解路由跟踪的工作机制,并给出基于Windows Socket API的程序框架与流程分析,可帮助读者理解Tracert背后的网络诊断逻辑。资源为独立doc文档,仅1个文件,大小约194KB,内容覆盖实验目的、设计要求、系统详细设计、程序流程图以及关键源代码片段。已有167人学习浏览,适合需要完成类似课程作业或希望深入理解tracert命令实现原理的入门及中级学习者。借助这份报告,读者不仅能看到Tracert从发送数据包到解析路由器响应的完整过程,还能获得原始套接字编程与ICMP报文处理的排错思路,为后续网络工具开发打下基础。

1. Tracert 程序设计报告:一份能直接跑的原始套接字课程设计

排查网络故障时,ping只能告诉你通不通,而tracert能告诉你断在哪一跳。这份课程的计算机网络设计报告,正是带你从零实现一个tracert命令的完整源码包:Windows 环境下用 VC6.0 编写,基于 SOCK_RAW 原始套接字和 ICMP 协议,逐跳递增 TTL 并向目标发送回显请求,通过解析路由器返回的 ICMP 超时报文,还原出数据报从源到目的经过的每一个 IP 地址和往返时延。适合正在做计算机网络课程设计、想搞懂 ICMP 或原始套接字编程的学生,也适合想看看 Tracert 底层到底怎么收包解码的开发者。它能直接编译运行,也能作为模板改成你自己的路由跟踪小工具。

2. 路由跟踪的设计逻辑:TTL、ICMP 与原始套接字为什么是这套组合

2.1 用 TTL 递减把「路径」变成逐跳回包

路由跟踪的核心难题是:你根本不知道数据报会经过哪些路由器,路由器之间也不会主动给你报信。协议栈给出的答案是 TTL(Time To Live)字段——IPv4 数据报头部里的一个一字节计数器,每经过一个路由器就减 1,减到 0 时路由器不再转发,而是向源地址回一个 ICMP 超时报文(类型 11,Time Exceeded),报文的源 IP 正好就是这台路由器的接口地址。

这就是 tracert 的全部秘密:第一次发一个 TTL=1 的包,第一跳路由器收到后 TTL 减为 0,回一个超时报文,你读到了第一跳的 IP;接着把 TTL 改成 2 再发,第一跳正常转发,第二跳把 TTL 减到 0,回超时报文,你读到了第二跳的 IP。如此循环,TTL 每递增一次就能「逼」路径上的第 N 个路由器开口说话,直到目标主机直接回应,或者 TTL 达到上限(Windows 的 tracert 默认是 30 跳)。这份报告的主循环就是按照这条逻辑走的:iTTL从 1 开始,每次setsockopt重新设置 IP_TTL,发完一个包后iTTL++,直到bReachDestHost置位或跳数耗尽。

2.2 选 ICMP 而不是 UDP:Windows 下 tracert 的两种实现路线

教材里常讲 traceroute 用 UDP 探测,目的是让目标主机回一个「端口不可达」(ICMP 类型 3 代码 3)来确定到达终点。但 Windows 系统自带的tracert命令默认用的是 ICMP Echo Request,也就是和ping同一种报文;Linux/macOS 的traceroute默认才是 UDP。这份报告选择了 ICMP 路线,有它的现实理由:目标主机收到 ICMP 回显请求后会直接回 Echo Reply,不需要应用层有对应端口在监听,所以对远端设备的适用面更广;同时代码里只需要处理 ICMP 一种协议,不用额外构造 UDP 头的 checksum,课程设计的编码量更可控。

带来的代价是解码时要同时处理两种 ICMP 报文:中间路由器回的ICMP_TIMEOUT(类型 11)和目标主机回的ICMP_ECHO_REPLY(类型 0)。注意recvfrom收到的是一个完整 IP 包,不是裸 ICMP 报文,所以要先跳过一个 IP 头才能拿到 ICMP 头;而为超时报文做解析时,报文载荷里还嵌着一个完整的原始 IP 头和原始 ICMP 头(用于让你确认这是哪个请求触发的超时),得再剥两层才能拿到原始的 id 和 seq。

2.3 这份程序的功能框架与输入输出约定

从报告正文能提炼出完整的功能边界:输入是一个 IP 地址或域名,输出是三条关键信息——跳数序号、每一跳的路由器 IP、到达该路由器所需的往返时延。中间路由器不回应时输出*和Request timed out。程序开头还会打印一句Tracing route to ... with a maximum of 30 hops,这和 Windows 内置 tracert 的格式几乎一致。

程序还把「目的探测」作为隐藏前置功能实现了:如果目标主机直接不可达(WSAEHOSTUNREACH),会在某一跳输出Destination host unreachable并结束;如果域名解析失败,则直接报错退出。整个程序的输入输出约定可以概括为一张表:

输入预期输出对应实现位置
合法 IP 地址逐跳 IP 与 RTT主循环 + DecodeIcmpResponse
合法域名先解析为 IP,再逐跳探测gethostbyname 分支
非法域名Could not resolve the host name参数检查分支
不可达目标Destination host unreachablesendto 返回 WSAEHOSTUNREACH 分支

3. 逐段拆解 Tracert 源码:从 Winsock 初始化到逐跳输出

3.1 参数解析、WSAStartup 与原始套接字的创建参数

入口main(int argc, char* argv[])先校验命令行参数,必须恰好给一个目标地址,否则打印用法提示并返回 -1。接着是 Windows Socket 编程的第一步——初始化 WinSock2 环境,用WSAStartup请求 2.2 版本的库:

WSADATA wsa; if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { cerr << "\nFailed to initialize the WinSock2 DLL\n" << "error code: " << WSAGetLastError() << endl; return -1; }

MAKEWORD(2, 2)表示要求加载 WinSock 2.2,WSADATA会带回实现细节。这一步不做,后面所有socket函数都会返回SOCKET_ERROR,错误码通常是 10093(WSA_NOT_INITIALIZED)。然后程序用inet_addr尝试把参数直接转成 32 位 IP,如果返回INADDR_NONE说明参数是域名,改走gethostbyname解析,并顺带打印目标域名和它对应的 IP。

原始套接字在这一段创建,参数值得逐字看:

SOCKET sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED);

AF_INET是 IPv4 地址族;SOCK_RAW指定原始套接字;第三个参数IPPROTO_ICMP声明这个原始套接字只处理 ICMP 协议,Windows 对原始套接字有安全限制,用IPPROTO_ICMP是当下最稳的合法用法。WSA_FLAG_OVERLAPPED允许套接字支持重叠 I/O,Windows 下后续的sendto/recvfrom可以正常工作;如果把最后两个参数换成(NULL, 0, 0),也能跑,只是少了一个异步扩展能力。

3.2 构造 ICMP Echo 请求:类型、校验和、序列号

发送缓冲区被设计成ICMP_HEADER + 32 字节数据的结构,ICMP_HEADER是自定义结构体,字段依次是type、code、cksum、id、seq。构造分两步,先把头部和载荷清零并填好固定字段,再单独计算校验和:

ICMP_HEADER* pIcmpHeader = (ICMP_HEADER*)IcmpSendBuf; pIcmpHeader->type = ICMP_ECHO_REQUEST; // 类型 8 pIcmpHeader->code = 0; pIcmpHeader->id = (USHORT)GetCurrentProcessId(); memset(IcmpSendBuf + sizeof(ICMP_HEADER), 'E', DEF_ICMP_DATA_SIZE);

type = 8表示这是回显请求,code = 0是回显请求的唯一合法代码。id字段在 Windows 实现里常取进程 ID,这样如果同时跑多个 Tracert 程序,接收端靠id就能区分是哪个进程的包。载荷填充'E'只是占位,实际内容无所谓,路由器只关心头。注意腾出空间放数据和首部,所以memset的起始位置跳过了ICMP_HEADER的大小。

校验和的计算是 ICMP 编程里最容易错的一步,报告里的GenerateChecksum实现是标准算法:

USHORT GenerateChecksum(USHORT* pBuf, int iSize) { unsigned long cksum = 0; while (iSize > 1) { cksum += *pBuf++; iSize -= sizeof(USHORT); } if (iSize) cksum += *(UCHAR*)pBuf; cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >> 16); return (USHORT)(~cksum); }

逻辑是:把所有 16 位字累加到一个 32 位变量里;如果数据长度是奇数,最后剩一个字节的低位也要累加;然后反复把高 16 位折叠回低 16 位,最后取反。要在算校验和之前把cksum字段清零,否则会把旧的校验和当成数据一起参与计算,结果必然错误。代码在主循环里就是这个顺序:((ICMP_HEADER*)IcmpSendBuf)->cksum = 0;然后htons(usSeqNo++)填序列号,最后算 checksum。序列号用来匹配每个 TTL 对应的请求和响应。

3.3 TTL 递增主循环:发送、等待与超时分支

主循环是这份程序最值得抄的部分,它用while (!bReachDestHost && iMaxHop--)控制整体跳数上限,循环体里每次先重新setsockopt设置IP_TTL:

setsockopt(sockRaw, IPPROTO_IP, IP_TTL, (char*)&iTTL, sizeof(iTTL));

IPPROTO_IP表示要改的是 IP 层的选项,IP_TTL对应 TTL 字段。这条路走通后,同一个 socket 反复发包,每轮的 TTL 都不同,这是实现 Tracert 的关键技巧——不用重建套接字。如果这段设置失败,最常见的原因是权限不足,后面避坑章会详细说。

发送时需要注意sendto的返回值处理。报告里对WSAEHOSTUNREACH(10065)做了专门分支,打印Destination host unreachable后直接收尾。其他错误码则交给外层统一处理退出。发送成功后进入核心的接收循环:

while (1) { iReadDataLen = recvfrom(sockRaw, IcmpRecvBuf, MAX_ICMP_PACKET_SIZE, 0, (sockaddr*)&from, &iFromLen); if (iReadDataLen != SOCKET_ERROR) { if (DecodeIcmpResponse(IcmpRecvBuf, iReadDataLen, stDecodeResult)) { if (stDecodeResult.dwIPaddr.s_addr == destSockAddr.sin_addr.s_addr) bReachDestHost = TRUE; cout << '\t' << inet_ntoa(stDecodeResult.dwIPaddr) << endl; break; } } else if (WSAGetLastError() == WSAETIMEDOUT) { cout << setw(9) << '*' << '\t' << "Request timed out." << endl; break; } ... }

注意这里是while (1)内层循环,原因在于原始套接字收到的是本机网卡上所有 ICMP 报文,不只是你发出的请求所对应的回包;如果收到的包解码后不匹配(比如别的程序触发的 ICMP 响应),就继续recvfrom,直到拿到匹配包或超时。超时值由之前setsockopt设置的SO_RCVTIMEO决定,默认 3000 毫秒。收到匹配包后,如果是目标地址回的,置bReachDestHost = TRUE,下次循环条件不满足就退出;不是目标地址回的(中间路由器超时报文),打印 IP 后 break,进入下一跳。

3.4 DecodeIcmpResponse 解码:两种回包与载荷偏移

DecodeIcmpResponse是另一段重点代码,它的任务是把收到的原始 IP 包拆成可读信息。第一步是算 IP 头长度:

IP_HEADER* pIpHdr = (IP_HEADER*)pBuf; int iIpHdrLen = pIpHdr->hdr_len * 4; if (iPacketSize < (int)(iIpHdrLen + sizeof(ICMP_HEADER))) return FALSE;

IPv4 头部长度字段的单位是 4 字节,普通 IP 头固定是 20 字节,所以hdr_len通常等于 5,乘 4 得 20。先做一次最小长度校验,防止后面越界访问。

接着按 ICMP 类型分两条路。如果是ICMP_ECHO_REPLY(类型 0),说明是目标主机的直接回应,id 和 seq 就在外层 ICMP 头里,直接取:

if (pIcmpHdr->type == ICMP_ECHO_REPLY) { usID = pIcmpHdr->id; usSquNo = pIcmpHdr->seq; }

如果是ICMP_TIMEOUT(类型 11),路由器返回的超时报文里没有你的请求信息,必须去载荷里找原始请求的头,这段代码展示了两层偏移的完整写法:

char* pInnerIpHdr = pBuf + iIpHdrLen + sizeof(ICMP_HEADER); int iInnerIPHdrLen = ((IP_HEADER*)pInnerIpHdr)->hdr_len * 4; ICMP_HEADER* pInnerIcmpHdr = (ICMP_HEADER*)(pInnerIpHdr + iInnerIPHdrLen); usID = pInnerIcmpHdr->id; usSquNo = pInnerIcmpHdr->seq;

第一次偏移:外层 IP 头长度 + ICMP 头大小,跳过 ICMP 头(类型 11 报文的头是固定的 8 字节),指向被封装在载荷里的原始 IP 头;第二次偏移:把这个内层 IP 头的长度再加进去,才指向原始 ICMP 头。潜在问题是如果内层 IP 头是 20 字节但外层 IP 头带可选字段,偏移量会有出入,所以标准做法是每次都从字段里读hdr_len而不是写死 20。

拿到usID和usSquNo后做双重匹配校验——必须等于当前进程 ID 和当前期望序列号stDecodeResult.usSeqNo,否则丢弃。这一步过滤掉了无关 ICMP 流量,是整个程序不输出乱数据的保证。校验通过后,把当前 IP 头的源 IP 写入结果结构体,用GetTickCount()的差值算出 RTT,按 ms 输出。

3.5 代码骨架:itracert.h 里缺失的类型定义补全

报告正文里main函数引用了itracert.h中的若干类型,但头文件内容没有全部给全。要复现这个程序,需要自己补上几个结构定义。按报告的用法,这几个定义几乎可以照搬:

#pragma pack(1) typedef struct _IP_HEADER { UCHAR hdr_len : 4; // IP 头长度,单位 4 字节 UCHAR version : 4; // 版本号 UCHAR tos; // 服务类型 USHORT total_len; // 总长度 USHORT id; // 标识 USHORT frag; // 标志与片偏移 UCHAR ttl; // 生存时间 UCHAR protocol; // 上层协议 USHORT checksum; // 首部校验和 ULONG sourceIP; // 源 IP ULONG destIP; // 目的 IP } IP_HEADER; typedef struct _ICMP_HEADER { BYTE type; // 类型:8=回显请求,0=回显应答,11=超时 BYTE code; // 代码 USHORT cksum; // ICMP 校验和 USHORT id; // 标识符 USHORT seq; // 序列号 } ICMP_HEADER; typedef struct _DECODE_RESULT { USHORT usSeqNo; // 期待收到的序列号 DWORD dwRoundTripTime; // 往返时间 in_addr dwIPaddr; // 返回源地址 } DECODE_RESULT;

#pragma pack(1)特别重要,它强制结构体按 1 字节对齐,避免编译器在USHORT前插入填充字节导致头部长度算错。IP 头用位域写hdr_len和version,是为了让你能像报告代码那样直接读pIpHdr->hdr_len;如果不想用位域,也可以用#define加位移提取,但对课程设计来说位域更直观。常量方面需要补DEF_MAX_HOP 30、DEF_ICMP_TIMEOUT 3000、DEF_ICMP_DATA_SIZE 32、MAX_ICMP_PACKET_SIZE 1024,这是 Windows tracert 的常用参数。

4. 编译、运行与结果验证:VC6.0 下的完整落地过程

4.1 工程配置:头文件顺序与 ws2_32.lib 链接

VC6.0 搭项目有两个容易卡住的配置点。第一,winsock2.h必须放在windows.h之前,最好放在所有系统头文件的最前面,否则会报一堆Winsock.h 已包含的重定义错误,因为旧版的winsock.h和winsock2.h不能共存。这份报告只包含了<iostream.h>、<iomanip.h>、<winsock2.h>、<ws2tcpip.h>,没有显式包含windows.h,这样反而更安全。

第二,原始套接字用到的是 WinSock2 的扩展函数(WSASocket、WSAStartup),需要在链接参数里加入ws2_32.lib。VC6 的操作路径是:Project 菜单 → Settings → Link 标签页 → Object/library modules 末尾加上ws2_32.lib,确认无误后编译。漏掉这一步的报错通常是LNK2001: unresolved external symbol __WSAStartup@8,看到这个错不要怀疑代码,直接去链接设置里补库。

编译通过后,用管理员身份打开命令行,在Debug目录下执行:

itracert.exe 192.168.1.1 itracert.exe www.baidu.com

注意 Windows 7 及以上系统,创建原始套接字必须要管理员权限。普通 cmd 运行会直接报错误码 10013(WSAEACCES),这一点血泪经验很多,操作时直接在「运行」里以管理员方式启动命令提示符即可。

4.2 三种测试场景的预期输出对照

测试目标选三个典型场景,能在十几分钟内验证程序的正确性。第一个是网关地址,比如本机默认网关192.168.1.1,正常输出第一跳就是网关自己的 IP,而且 RTT 通常在 1ms 左右或显示<1 ms,因为走的是局域网内部,不经过多少处理逻辑;第二个是公网目标,比如www.baidu.com,输出会出现多个条目,对应从本地到电信/联通骨干的路径;第三个是故意探测一个不存在的内网 IP,比如192.168.1.200,预期能看到第一跳路由回超时后,后面连续输出* Request timed out直到 30 跳上限。

输出格式上报告做了对齐处理:跳数用setw(3)占三列,RTT 用setw(6)占六列,超时打印*。<1 ms的处理是特意保留的——Windows 的 tracert 同样会把小于 1ms 的时延显示为<1 ms,这是为了避免用户看到 0ms 误以为计时失效。如果某个中间路由器不回 ICMP 超时报文(某些网络设备配置了抑制 ICMP 生成的策略),对应跳数会显示* Request timed out,但程序会继续往下探测,不阻塞整条链路。

4.3 和系统 tracert 逐跳对比验证

复现完成后,把自研程序和系统自带的tracert命令放在一起对比,是最好的正确性验证。右键管理员运行两次命令,一次是系统tracert -d -h 30 <目标>(-d关闭域名反查,避免显示差异干扰判断),一次是itracert.exe同一目标:

C:\> tracert -d -h 30 223.5.5.5 C:\> itracert.exe 223.5.5.5

对比两张输出的第一列跳数和第二列 IP 地址。正常情况下每一跳的 IP 应该完全相同,只有 RTT 有几十毫秒以内的波动。系统 tracert 每一跳默认发三次包,自研程序一跳只发一次,RTT 不逐一相等是正常的,但 IP 序必须一致。如果出现跳数比系统多或少,优先检查自己的超时设置是否太短导致个别跳被跳过,以及防火墙是否放行了两者的 ICMP 流量。这一步对比完,基本可以确认程序的 ICMP 构造、TTL 设置和解码逻辑都是对的。

5. 避坑指南:原始套接字课程的六个高发翻车点

5.1 一运行就报 10013,管理员权限绕不过去

现象:程序编译链接都通过,但一执行就提示Failed to create a raw socket, error code: 10013,或者setsockopt返回失败。 原因:Windows 从 XP SP2 开始默认限制创建原始套接字,SOCK_RAW 只能在管理员权限下创建。普通双击或在普通 cmd 里跑,系统直接拒绝。 解决:以管理员身份运行,右键「以管理员身份运行」打开命令行,或者在代码里加一段IsUserAnAdmin检查,提示用户提权后再继续。这是这类程序最常见的入门坎,不算代码问题。

5.2 所有跳数都是 Request timed out,防火墙在拦

现象:程序能跑,第一跳就收不到任何回包,全链路输出*,但系统自带 tracert 也一样超时。 原因:Windows 防火墙默认拦截入站 ICMP Echo 报文,如果你探测的目标(或中间路由器)开启了防火墙,不回应 ICMP 是正常行为。 解决:实验室环境可以临时在防火墙「入站规则」中放行「回显请求 (ICMPv4-In)」,实测后记得恢复。真实公网环境部分路由器厂商确实不回应 ICMP 超时报文,这种无法强求,按「路径黑洞」处理即可。

5.3 解码出的 IP 不对,载荷偏移算错了

现象:能收到包,但打印出来的 IP 明显不对,比如永远是某个内网地址,或者 IP 和跳数对应关系混乱。 原因:ICMP_TIMEOUT报文里内嵌了原始请求的 IP 头和 ICMP 头,需要先跳过外层 IP 头 + 外层 ICMP 头,再跳过内层 IP 头,才能读到正确的内层 ICMP 头。漏掉任何一层,取到的 id 和 seq 都是错的,过滤直接失败。 解决:画一张字节偏移图对照着写:外层 IP 头(长度读hdr_len * 4)→ 外层 ICMP 头(8 字节)→ 内层 IP 头(再次读hdr_len * 4)→ 内层 ICMP 头。逐层打印偏移值核对一遍,确认无误再继续。

5.4 校验和算完丢包,顺序不对

现象:程序发出请求后一直超时,但用 Wireshark 抓包发现本机发出的 ICMP 包 checksum 是 0,或收到了但被目标丢弃。 原因:计算校验和之前没有把 ICMP 头的cksum字段清零。校验和的算法要求先置零再计算,否则旧值参与累加,结果自然错;另一个常见原因是字节序没转,网络字节序和主机字节序混用。 解决:严格按发送顺序来——清零cksum→ 填seq→ 调GenerateChecksum→ 再发送。如果你改了数据长度,每次都要重新算,不能用上一次的 checksum 复用。

5.5 收到一堆不相关的 ICMP 包,序列号没过滤

现象:输出里偶尔混入奇怪的 IP 或跳数错乱,不是固定的超时。 原因:原始套接字会把网卡收到的所有 ICMP 报文都拷贝一份给程序,不匹配请求的包也会进入recvfrom。如果不做过滤直接 break,打印的就是别人的回包。 解决:DecodeIcmpResponse里必须同时校验id和seq,id 等于当前进程 ID、seq 等于当前发送序号才接收。这也是为什么程序要循环recvfrom而不是收到一个就跳出——不匹配就继续读,直到匹配或超时。

5.6 VC6 里 winsock2.h 和 windows.h 打架

现象:编译时报一堆redefinition错误,定位到winsock2.h和winsock.h的宏冲突。 原因:winsock2.h是后来版本,老 VC6 的 SDK 同时带有旧版winsock.h。如果代码在包含windows.h之后再包含winsock2.h,windows.h内部的winsock.h会被先加载,导致重定义。 解决:把#include <winsock2.h>提到所有系统头文件的最前面;必要时定义WIN32_LEAN_AND_MEAN来裁剪windows.h里不相关的部分,避开winsock.h的自动引入。链接时别忘了加ws2_32.lib。

6. 把课程设计改成可复用诊断工具:三次探测取均值与反查主机名

报告原始版本每个 TTL 只发一个请求包,延时会受瞬时波动影响。我的习惯是随手把它升级成「每跳探测三次取平均」,这是从 Linux traceroute 学来的做法。改动量很小:在外层while循环里对同一个 TTL 加一个内层 for 循环,重复发送三次相同 seq 的 ICMP 包。代码骨架如下:

for (int probe = 0; probe < 3; probe++) { // 设置 IP_TTL,填充 ICMP 头,记录发送时间 sendto(sockRaw, IcmpSendBuf, sizeof(IcmpSendBuf), 0, (sockaddr*)&destSockAddr, sizeof(destSockAddr)); // 循环 recvfrom + DecodeIcmpResponse,直到匹配或超时 // 累计 RTT,最后除以 3 输出平均往返时延 } cout << '\t' << inet_ntoa(stDecodeResult.dwIPaddr) << endl;

三次探测的附加价值不只是平均 RTT,还能观察某条链路有没有丢包——三次里有一次超时,说明这条路径上有随机丢包,对判断无线网络或拥塞链路很有意义。第二个顺手改造是给每一跳的 IP 加主机名反查,用gethostbyaddr把路由器 IP 翻译成域名,很多运维同学习惯直接看节点名判断是哪个城市的出口,这个功能对讲清楚「网络结构」很有帮助。注意反查有 DNS 延迟,建议只在显示阶段做,且失败时静默回退到纯 IP 显示。

验证这套改造是否成功,仍然拿系统tracert做基准对比:同一目标下三次探测计算出的平均 RTT 应当和系统输出同跳时延在一个量级,IP 顺序完全一致。如果换了另一门语言重写——比如现在想用 Go 做跨平台版本,思路也完全一样:TTL 递增、ICMP Echo、超时解码这三块逻辑可以平移到golang.org/x/net/ipv4的SetTTL和icmp包,只是省去了手动构建 IP 头这一步。从那以后我每次写网络诊断类程序,都会强制走一遍「先抓包看本机发出的报文对不对 → 再对端看回包类型 → 最后对比系统工具的输出」这个流程,权限、防火墙、偏移这三类坑基本能在十分钟内定位。尤其是 ICMP 和原始套接字,不抓包全靠猜是纯玄学,抓到包一切都有定论。希望这份报告和代码能帮你少走几趟弯路。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 5:06:03

2026工业AI落地实录:从质量检测到工艺优化的真实图景

1. 工业AI从概念到落地的真实图景1.1 为什么2026年是个关键节点2026年这个时间点很微妙。往前推五年&#xff0c;工业AI还停留在PPT和概念验证阶段&#xff0c;几乎每场行业展会都在讲“智能制造”“黑灯工厂”&#xff0c;但真正走进车间、跑在产线上的项目屈指可数。往后看&a…

作者头像 李华
网站建设 2026/9/30 5:04:44

I2C多主机仲裁与时钟延展:开漏输出下的总线共享机制详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 5:04:39

Claude Code多线程实战:Agent View与Teams配合Polter编排

1. 多线程玩法到底在解决什么问题第一次接触 Claude Code 的多线程能力时&#xff0c;我其实是有点懵的。官方文档里 Agent View 和 Agent Teams 这两个词反复出现&#xff0c;但真正落到实际项目里&#xff0c;到底什么时候该用哪个、怎么组合&#xff0c;文档说得并不算清楚。…

作者头像 李华
网站建设 2026/9/30 5:04:19

格林、高斯、斯托克斯公式与哈密顿算子全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 5:03:23

随机变量从入门到精通:概率统计与数据分析的核心桥梁

第一次接触“随机变量”这个词时&#xff0c;我脑子里冒出来的想法是&#xff1a;随机就随机&#xff0c;变量就变量&#xff0c;为什么非要凑成四个字。后来被概率统计反复折磨&#xff0c;才意识到这短短四个字&#xff0c;其实是整个概率论和统计学之间的一架桥——它把“事…

作者头像 李华
网站建设 2026/9/30 5:02:41

AI Agent实战:从0到1搭建自动化工作流与避坑指南

1. 先说清楚我到底让AI Agent干了什么活去年年底我开始认真折腾AI Agent&#xff0c;动机特别朴素——我手上有一堆重复性高、但又必须有人盯着的杂活&#xff0c;比如每天早上整理前一天的社群消息、把散落在各个文档里的需求汇总成周报、盯着几个数据源的变化然后推送到群里、…

作者头像 李华