ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

TCP/IP协议栈手写调试日志:从抓包到校验和手工计算

TCP/IP协议栈手写调试日志:从抓包到校验和手工计算 简介本资源是东南大学自动化学院《信息通信网络概论》课程的第四次实验报告面向计算机网络初学者及高校相关专业学生聚焦TCP/IP与UDP/IP双协议栈下的网络通信应用开发实践。报告完整覆盖实验目的、原理、方案步骤、设备配置、界面设计、功能实现含连接管理、消息交互、自定义字符画、系统调用等扩展功能及详细实验记录与总结并附有关键代码片段有助于深入理解Socket编程、客户机/服务器模型及协议差异。资源为单个PDF文件大小仅20KB内容精炼、结构清晰含目录导航与多层级实验模块便于快速查阅与复现。目前已有88人学习下载适合课程复习、实验参考或网络编程入门实践。1. 这不是一份普通PDF东南大学计算机网络第四次实验报告本质是TCP/IP协议栈的“手写调试日志”你打开这份名为《东南大学计算机网络第四次实验报告.pdf》的文件时真正看到的不是格式工整的Word转PDF而是一份基于真实抓包、手动构造、逐层验证的TCP/IP协议行为实录。它不讲抽象模型只呈现“当我在SEU机房用Wireshark抓到这个SYN-ACK包时为什么校验和是0x4a2f——因为我在C语言socket里故意把IP头TTL设成127而Linux内核在转发前重写了它”。这份报告对应的是谢希仁《计算机网络》教材中“运输层”核心章节的落地验证聚焦TCP连接建立/释放全过程、UDP校验和手工计算、ICMP差错报文触发机制三大硬核动作。适合正在啃《自顶向下》第3章、被头歌实训平台里“TCP三次握手模拟器”卡住、或准备408统考运输层大题却总缺实感的同学——它不教你背概念它逼你亲手把字节序、网络字节序、伪首部、FIN等待时间这些黑匣子掰开、看透、再焊回去。2. 从抓包到手算还原TCP三次握手的每一个字节真相2.1 实验环境复现SEU机房标准配置与最小化依赖东南大学计算机网络实验课通常部署在统一的Linux教学机房CentOS 7.6 kernel 3.10所有学生使用同一套预装工具链tcpdump非Wireshark GUI因需命令行可复现、ncnetcat、gccC99标准、python3.6仅用于辅助计算。关键点在于禁用所有中间件干扰实验前必须关闭防火墙sudo systemctl stop firewalld、禁用NetworkManagersudo systemctl stop NetworkManager并确认/proc/sys/net/ipv4/ip_forward为0——这不是为了“安全”而是确保你抓到的每个包都来自本机协议栈而非被内核转发或NAT篡改。我当年在四牌楼校区主楼305机房做这个实验时有3个同学因没关firewalld抓到的SYN包里多出一个TCP Option: MSS1460字段导致后续校验和计算全错——这恰恰说明实验报告的价值不在结果正确而在你能定位到哪一行代码/哪个内核参数让结果“意外”偏离预期。2.2 抓包命令与过滤逻辑为什么必须用-i lo而不是-i eth0# 正确命令监听回环接口排除网关/ARP干扰 sudo tcpdump -i lo -nn -X tcp port 8080 -w tcp_handshake.pcap # 错误示范监听物理网卡混入大量ARP、DNS、HTTP流量 # sudo tcpdump -i eth0 -nn -X tcp port 8080 -w tcp_handshake.pcap提示SEU实验要求所有通信走本地回环127.0.0.1因为只有lo接口能保证“发送即接收”避免网络延迟、丢包、重传等外部变量污染TCP状态机观察。-nn禁用DNS解析防止额外UDP查询干扰-X输出十六进制ASCII双视图——这是你后续手工计算校验和的唯一依据。tcp port 8080过滤器必须精确到端口因为实验脚本默认绑定8080见下节C代码若用tcp泛过滤你会抓到sshd、cron等后台进程的TCP包直接毁掉数据集纯净度。2.3 手工计算TCP校验和从Wireshark截图到C语言实现TCP校验和计算是本实验最易翻车环节。它不是简单对TCP段求和而是包含伪首部pseudo-header TCP首部 TCP数据三部分的16位反码和。伪首部由IP源/目的地址、协议号6、TCP长度组成且必须按网络字节序大端排列。以下是SEU实验报告中要求的手工验证步骤从tcpdump -X输出中提取SYN包的原始字节示例截取0x0000: 4500 003c 0000 4000 4006 0000 7f00 0001 E............ 0x0010: 7f00 0001 1f90 1f90 0000 0000 0000 0000 ................ 0x0020: 6002 2000 97be 0000 0204 05b4 0402 080a ...............构造伪首部IP src127.0.0.1, dst127.0.0.1, proto6, TCP len407f00 0001 7f00 0001 0006 0028 # 注意TCP len400x0028高位在前拼接伪首部TCP首部0x0010~0x002f共32字节填充字节若奇数长度补07f00 0001 7f00 0001 0006 0028 0000 0000 ...完整40字节按16位分组求和进位回卷取反码 → 得到校验和字段值为避免手工计算出错SEU推荐用C语言实现验证实验报告要求附代码#include stdio.h #include stdint.h #include arpa/inet.h uint16_t tcp_checksum(uint8_t *data, size_t len) { uint32_t sum 0; uint16_t *ptr (uint16_t*)data; // 按16位累加 while (len 1) { sum ntohs(*ptr); len - 2; } if (len 1) { // 奇数长度补0 sum *(uint8_t*)ptr; } // 进位回卷 while (sum 16) { sum (sum 0xffff) (sum 16); } return ~sum; // 取反码 } int main() { // 示例伪首部TCP首部已按网络字节序排列 uint8_t pkt[] { 0x7f,0x00,0x00,0x01, 0x7f,0x00,0x00,0x01, // IP src/dst 0x00,0x06, 0x00,0x28, // proto6, TCP len40 0x1f,0x90, 0x1f,0x90, // src/dst port 0x00,0x00, 0x00,0x00, // seq num 0x00,0x00, 0x00,0x00, // ack num 0x60,0x02, 0x20,0x00, // data offset6, flagsSYN, window8192 0x00,0x00, 0x00,0x00, // checksum0x0000待填 0x02,0x04, 0x05,0xb4, // MSS option 0x04,0x02, 0x08,0x0a // SACK option }; uint16_t chk tcp_checksum(pkt, sizeof(pkt)); printf(TCP Checksum: 0x%04x\n, ntohs(chk)); // 输出应为0x97be与抓包一致 return 0; }参数说明ntohs()将主机字节序转网络字节序tcp_checksum()函数严格遵循RFC 793定义pkt数组必须按网络字节序排列否则结果必错sizeof(pkt)必须等于伪首部(12)TCP首部(20)选项(8)40字节少一字节校验和就失效。我当年在调试时发现pkt里checksum字段填了0x0000但计算时仍要参与求和——这是RFC强制要求不是占位符。3. UDP校验和实战为什么“禁用校验和”反而暴露协议本质3.1 UDP实验设计逻辑用“错误”验证“正确”SEU第四次实验刻意设置了一个反直觉任务先用setsockopt(sockfd, IPPROTO_UDP, UDP_CORK, on, sizeof(on))禁用UDP校验和发送一个故意构造的错误校验和包再对比启用校验和时的行为差异。这不是教你怎么绕过检查而是让你看清UDP校验和是端到端的完整性保护而非链路层责任。当校验和被禁用时Linux内核会将udp_hdr-check置0此时接收方协议栈不会丢弃该包符合RFC 768但应用层recvfrom()读到的数据可能已被链路层损坏——这正是UDP“尽力交付”的血泪注解。3.2 构造UDP错误校验和包C语言socket底层操作#include sys/socket.h #include netinet/in.h #include arpa/inet.h #include string.h int main() { int sockfd socket(AF_INET, SOCK_DGRAM, 0); struct sockaddr_in dest; memset(dest, 0, sizeof(dest)); dest.sin_family AF_INET; dest.sin_port htons(8080); inet_pton(AF_INET, 127.0.0.1, dest.sin_addr); // 关键禁用UDP校验和 int off 0; setsockopt(sockfd, IPPROTO_UDP, UDP_CORK, off, sizeof(off)); char payload[] SEU_NETLAB; // 发送时故意让校验和字段为0x0000实际应为有效值 sendto(sockfd, payload, sizeof(payload), 0, (struct sockaddr*)dest, sizeof(dest)); close(sockfd); return 0; }注意UDP_CORK在此处是误用正确应为IPPROTO_UDP, UDP_NO_CHECKSUM6_TX但SEU实验故意保留此历史写法因为旧版CentOS内核3.10中UDP_CORK确实影响UDP校验和生成逻辑。真正的坑在于sendto()返回成功不代表包被接收方校验通过——你需要用tcpdump -i lo udp port 8080 -XX抓包查看UDP首部Checksum字段是否真为0x0000再用另一端nc -ul 8080接收观察是否收到乱码。这才是实验报告要记录的“现象→原因→结论”闭环。3.3 UDP校验和手工验证比TCP更简单的伪首部结构UDP伪首部仅含4字节源IP、4字节目的IP、1字节协议号、2字节UDP长度不含IP首部共12字节。其计算逻辑与TCP一致但UDP允许校验和为0表示不计算RFC 768 Section 3.1。SEU实验报告要求你对比两种场景场景A启用校验和payloadSEU → 计算得checksum0x1a2b场景B禁用校验和payloadSEU → 抓包显示checksum0x0000关键验证点当场景B中你手动修改payload为SEU\x00增加空字节再启用校验和checksum变为0x1a2c——这证明校验和敏感于每个字节且0x0000不是“无效”而是“显式声明不校验”。这个细节常被忽略却是理解UDP设计哲学的核心。4. 避坑指南SEU计算机网络实验报告里最常踩的5个深坑4.1 现象Wireshark显示“TCP Out-Of-Order”但tcpdump无此标记原因Wireshark默认启用TCP流重组TCP Reassembly而tcpdump只输出原始帧。SEU实验要求用tcpdump原始输出若你用Wireshark打开pcap并依赖其“Out-Of-Order”标注会导致分析结论错误——因为该标记是Wireshark基于时间戳和seq号推测的非协议栈真实行为。解决实验报告中所有分析必须基于tcpdump -X十六进制输出用seq/ack字段手工追踪状态机Wireshark仅作可视化辅助不作为判断依据。4.2 现象setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, on, sizeof(on))后小包仍合并发送原因TCP_NODELAY禁用Nagle算法但仅对小于MSS的包生效。SEU机房网卡MSS1448若你发送1500字节payload内核仍会拆分成两个TCP段144852且第二个段可能被延迟。解决实验必须用send()发送≤1400字节数据并用tcpdump确认每个send()调用对应一个独立TCP段Flags0x02即SYN或0x18即ACKPSHFIN。4.3 现象UDP校验和计算结果与Wireshark不一致差0x0001原因未处理“奇数长度填充”。UDP长度字段包含首部8字节数据长度若数据长为奇数如11字节伪首部UDP首部数据总长为奇数必须在末尾补一个0x00字节参与校验和计算但该字节不计入UDP长度字段。解决手工计算时先算total_len 12(pseudo) 8(UDP hdr) data_len若total_len % 2 1则补0x00Wireshark的校验和计算器自动处理此填充你必须同步。4.4 现象nc -l 8080接收不到自己sendto()的UDP包原因nc默认绑定INADDR_ANY0.0.0.0而你的sendto()目标是127.0.0.1。在SEU机房多网卡环境下lo/eth0nc可能监听在eth0上导致回环包被丢弃。解决强制nc -l -u -s 127.0.0.1 8080-s指定源地址绑定确保与sendto()目标IP严格匹配。4.5 现象实验报告要求“分析TIME_WAIT状态持续时间”但ss -tan显示timewait状态为0原因ss默认只显示当前连接而TIME_WAIT是连接关闭后的残留状态需加-o选项显示定时器信息。且SEU实验要求观察netstat -an | grep :8080中TIME_WAIT行的Recv-Q列应为0和Send-Q列应为0而非ss的state列。解决用netstat -an --timers | grep :8080关注timer:(keepalive,30min,0)字段——这才是SEU评分标准中“TIME_WAIT超时时间”的直接证据。5. 进阶验证用Python自动化校验TCP状态机与实验报告一致性5.1 构建TCP状态迁移验证器从pcap到状态图SEU实验报告最后一问常是“画出本次实验TCP连接的状态迁移图”。手动画易出错我用PythonScapy实现了自动化验证脚本它能从tcp_handshake.pcap中提取所有TCP包按src_ip:src_port → dst_ip:dst_port分组再根据Flags字段SYN/ACK/FIN/RST和seq/ack关系生成DOT格式状态图。核心逻辑如下from scapy.all import * import graphviz def parse_tcp_pcap(pcap_file): packets rdpcap(pcap_file) tcp_streams {} for pkt in packets: if TCP in pkt: key (pkt[IP].src, pkt[TCP].sport, pkt[IP].dst, pkt[TCP].dport) rev_key (pkt[IP].dst, pkt[TCP].dport, pkt[IP].src, pkt[TCP].sport) # 归一化流方向客户端→服务端 if key not in tcp_streams and rev_key not in tcp_streams: tcp_streams[key] [] tcp_streams[key].append(pkt[TCP]) return tcp_streams def build_state_graph(stream_pkts): # 初始化状态CLOSED states [CLOSED] transitions [] for pkt in stream_pkts: flags pkt.flags if flags 0x02 and not (flags 0x10): # SYN only transitions.append((CLOSED, SYN_SENT)) elif flags 0x12: # SYNACK transitions.append((SYN_SENT, ESTABLISHED)) elif flags 0x11: # FINACK transitions.append((ESTABLISHED, FIN_WAIT_1)) transitions.append((FIN_WAIT_1, TIME_WAIT)) # 去重并构建Graphviz dot graphviz.Digraph(commentTCP State Machine) for s in set([t[0] for t in transitions] [t[1] for t in transitions]): dot.node(s) for src, dst in set(transitions): dot.edge(src, dst) return dot # 使用示例 streams parse_tcp_pcap(tcp_handshake.pcap) for key, pkts in streams.items(): g build_state_graph(pkts) g.render(ftcp_state_{key}, formatpng, cleanupTrue)参数说明rdpcap()读取pcappkt[TCP].flags是8位标志字段0x02SYN, 0x10ACK, 0x01FINbuild_state_graph()仅处理标准路径SYN→SYN-ACK→ACK→FIN→FIN-ACK→ACK忽略RST等异常分支——因为SEU实验要求验证“理想路径”。生成的PNG图可直接插入报告比手绘更严谨。5.2 实验报告评分隐性规则为什么“截图位置”比“内容”更重要SEU计算机网络实验报告评分表中“结果分析”占40分其中截图规范性占15分。具体包括截图类型必须包含字段扣分点tcpdump -X输出必须显示0x0000:行及对应ASCII列且0x0000行需包含IP首部前16字节缺失ASCII列扣3分0x0000行未覆盖IP首部扣5分netstat -an输出必须显示State列和Recv-Q/Send-Q列且TIME_WAIT行需完整可见未显示Recv-Q列扣2分TIME_WAIT行被截断扣4分C程序编译输出必须包含gcc -o client client.c和./client两行且./client后需有Segmentation fault或success字样无./client执行行扣3分无运行结果扣2分我当年因tcpdump截图只截了0x0010行漏掉0x0000的IP版本字段被扣5分——教授批注“看不到IP首部如何验证TTL字段” 这提醒我们实验报告不是成果展示而是过程证据链。每张截图都是法庭证物缺一不可。5.3 终极技巧用strace追踪socket系统调用定位协议栈行为边界当你对某个TCP行为困惑时例如“为什么close()后立即bind()失败”strace是SEU机房最被低估的工具。它能告诉你内核到底执行了什么# 追踪client程序的所有socket相关系统调用 strace -e tracesocket,bind,connect,sendto,recvfrom,close ./client 21 | grep -E (socket|bind|connect|close) # 输出示例 socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) 3 connect(3, {sa_familyAF_INET, sin_porthtons(8080), sin_addrinet_addr(127.0.0.1)}, 16) 0 close(3) 0 # 注意这里close返回0但TIME_WAIT状态仍在内核中维持关键洞察close()系统调用返回成功只表示用户态文件描述符释放不表示TCP连接立即消失。TIME_WAIT是内核协议栈行为strace看不到但netstat -tn | grep :8080能看到。这个认知差正是SEU实验想教会你的应用层API与协议栈实现之间永远隔着一层内核黑匣子。我养成了一个习惯每次close()后必跟一句sleep(1); system(netstat -tn | grep :8080)亲眼确认TIME_WAIT出现——这比背10遍RFC文档都管用。希望帮到你。本文还有配套的精品资源点击获取
返回列表