如何用tcpdump捕获TCP/IP分片?Python分片异常排查
问题背景
已知网络MTU为1500字节,TCP的MSS为1460字节。我理解当recv函数缓冲区大于1460字节时TCP会分片,于是编写了简单的回显程序,想用tcpdump查看分片情况,但发现缓冲区较小时没有分片显示,直到缓冲区约20K时才出现分片。
按8192字节缓冲区计算,理论上应该分为1460*5 + 892的分片,但实际tcpdump截图中的情况并非如此,我怀疑是否由DF(Don't Fragment)标志导致,想知道Python默认是否设置了该套接字选项?
测试代码
服务端代码
import socket import sys import os addr = ('10.0.0.2', 10086) server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(addr) server.listen(5) while True: connfd, addr = server.accept() print 'connection ip:', addr data = connfd.recv(8192);
客户端代码
import socket import os import sys addr = ('10.0.0.2', 10086) client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(addr) data = ''; for num in range(0,8192): data += '1' client.sendall(bytes(data))
使用的tcpdump命令
sudo tcpdump -i lo port 10086 -s 1514 -v
问题解答
先帮你理清几个容易混淆的关键概念,再逐个解答你的疑问:
1. 核心概念纠正:TCP不分片,分片是IP层的行为
TCP会根据MSS(最大段大小,这里是1460)拆分应用层数据,发送多个TCP段;而IP分片是当IP数据包大小超过接口MTU时,IP层把数据包拆分成多个小数据包的行为。这两者是完全不同的机制,你看到的tcpdump输出里的“多段”,大概率是TCP分段,而非IP分片。
2. 为什么小缓冲区时看不到“分片”?
你用的是回环接口(lo),Linux系统里回环接口的默认MTU是65536,远大于1500。哪怕TCP发送几个MSS大小的段,整个IP数据包的大小也远低于回环MTU,所以IP层完全不需要分片。而你说的“20K左右才出现分片”,其实是TCP因为滑动窗口、Nagle算法等机制,开始发送更大的批量数据,产生了更多TCP段,而非IP分片。
3. Python套接字默认的DF标志设置
在Linux系统上,Python的socket模块默认不会开启DF标志(对应IP_DONTFRAG套接字选项)。你可以用一行代码验证:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 查看IP_DONTFRAG选项(Linux上该选项的编号是16) print(s.getsockopt(socket.IPPROTO_IP, 16))
默认返回0,表示DF标志未设置,IP层有权对超过MTU的数据包进行分片。
4. 为什么8192字节数据没有按预期分段?
这是TCP的传输机制导致的:
- Nagle算法:默认开启的Nagle算法会合并小的TCP段,直到积累到足够大小(比如接近MSS)或者收到对端的ACK才发送,这会让实际发送的段数量和大小和预期不符。
- 滑动窗口:TCP的滑动窗口大小会影响发送端一次能发送的数据量,可能导致段的拆分方式变化。
- 系统TCP参数:比如
tcp_max_segment等系统参数也会影响实际的段大小。
如果真的想测试IP分片,建议:
- 切换到物理网络接口(比如
eth0),确保该接口的MTU是1500。 - 构造一个超过MTU的IP数据包(比如通过UDP发送大于1472字节的数据,因为UDP头部8字节+IP头部20字节=28,1500-28=1472),这样就能看到IP分片了。
内容的提问来源于stack exchange,提问作者Tony Chen

