tcpdump抓包实战:从三次握手到重传与RST的TCP排障指南 处理TCP问题最怕的就是两眼一抹黑。应用报“连接超时”数据库那边说“我没看到异常”防火墙说“我没拦”交换机说“我这边流量正常”——所有人都在踢皮球但问题确实存在。这时候你手上最趁手的工具就是tcpdump。它不关心你业务层的各种解释只告诉你一个事实数据包到底到没到回了没有哪一端先放弃的。tcpdump是Linux下最经典的命令行抓包工具零依赖、上手快几乎所有发行版都自带或一行命令就能装。它特别适合两类人一类是后端开发和运维排查“连不上”“响应慢”这类高频问题另一类是刚接触网络协议栈的初学者用它把课本上的三次握手、重传、四次挥手真正在网络上复现一遍。这篇文章我不会给你堆参数手册而是直接带你走一遍我实际排查TCP问题时的完整思路和抓包命令看完你就能自己上手。1. 动手之前搞懂tcpdump到底在抓什么1.1 tcpdump的工作方式与抓包位置的选择tcpdump的原理并不神秘它在内核里通过libpcap库把网卡上流经的数据包复制一份到用户态然后按你给的过滤规则进行匹配和输出。注意“复制”这个词tcpdump本身不修改、不拦截任何数据包它只是一个被动的观察者。这一点很重要因为很多人出了故障不敢抓包怕抓包影响线上流量实际上只要过滤规则得当tcpdump的开销很小。抓包位置的选择决定了你诊断问题的有效程度。最简单的经验是离问题点越近越好。客户端连不上服务端就要在客户端抓“出去的包”和“回来的包”服务端说没收到请求就要在服务端抓“进来的包”。更复杂的情况是客户端和服务端都在你控制范围之外中间隔着别人的网络那你能做的只有在自己这一侧抓包然后结合双端的时间戳和现象做推断。我曾经排过一个跨地域访问超时的问题客户端抓包能看到SYN发出但始终没有SYN-ACK服务端却说自己收到了连接请求也回了包。两边各执一词最后才发现是中间链路设备丢弃了特定大小的包——这不抓包还真没法定位。1.2 最常用的参数与过滤表达式一条条说清楚先把最常用的几个参数记住日常排障90%的场景都靠它们。tcpdump -i any -nn -s 0 -w /tmp/capture.pcap port 8080-i any抓所有网卡避免你搞不清流量走的是eth0还是bond0。-nn不做域名和端口名解析。DNS解析在排查时非常干扰视线而且如果解析超时还会拖慢抓包所以一律关闭。-s 0抓完整包。默认只抓前96字节对于分析TCP载荷不够用直接设0抓全量。-w写文件。终端上滚动输出的包事后分析效率极低先落盘再慢慢看。port 8080端口过滤最常用的过滤维度。过滤表达式是tcpdump的灵魂但初学者很容易被复杂的BPF语法吓到。其实日常需要记住的就这几个需求表达式只看某台主机的流量host 10.1.2.3只看某个端口的流量port 8080只看某对地址和端口host 10.1.2.3 and port 8080只看TCP协议tcp只看SYN包tcp[tcpflags] tcp-syn ! 0只看RST包tcp[tcpflags] tcp-rst ! 0为什么后面三个过滤要写成tcp[tcpflags] tcp-syn这种形式这是BPF语法里对TCP头部标志位的位运算写法。TCP头里每一个标志位占一个bitSYN是00000010bit 1ACK是00010000bit 4。tcp[tcpflags]取出整个标志位字节然后和目标标志位做按位与结果不为0就说明该标志位被置位了。这里有个容易踩的坑tcp[tcpflags] tcp-syn ! 0匹配到的不仅仅是纯SYN包SYN-ACK也会被命中因为SYN-ACK里SYN位也是1。如果你只想看“新建连接请求”的纯SYN需要排除ACK位写成tcp[tcpflags] (tcp-syn|tcp-ack) tcp-syn。这个细节在分析握手超时时特别容易混淆我在后面实战部分会再强调。还有一个参数值得单独说说-c。指定抓多少个包后自动停止比如tcpdump -c 10。不要小看它在生产环境排查时你往往只需要确认前几个握手包和重传包长什么样-c能避免你按CtrlC之前抓入一堆无用流量。配合timeout命令使用效果更佳timeout 10 tcpdump -i eth0 -nn port 8080 -w /tmp/a.pcap10秒后自动结束不用干等。2. TCP问题诊断的思路框架先明确你想验证什么2.1 连接建立失败怎么看三次握手TCP连接建立失败是排查对象中最常见的。应用日志里通常表现为“Connection timed out”或者“Connection refused”两者对应的数据包现象完全不同。“Connection timed out”说明SYN发出去了但一直没收到SYN-ACK。这时抓包要看三个层面第一本机是不是真的把SYN发出去了第二对端有没有回SYN-ACK第三如果对端回了这个回包有没有到达本机网卡三层对应三个不同的故障域本机路由或防火墙、对端主机状态或防火墙、中间链路。判断方法很简单在本机抓包看有没有SYN重传再看有没有SYN-ACK乱序到达。如果SYN发了一堆一个回应都没有问题大概率不在本机在对端或链路。“Connection refused”则说明你收到了RST包。RST是TCP协议里“硬拒绝”的信号收到它意味着对端确实在线但端口上没有监听或者内核策略拒绝了你。分析这个问题时抓包目的不是看“有没有包”而是确认谁发的RST、发的时机这比单纯看日志要准确得多。有个Linux参数你必须先知道net.ipv4.tcp_syn_retries。它控制内核SYN重试的次数默认值是6。也就是说第一次SYN发出后大概1秒没有回应会重试之后以约指数退避的节奏继续重试总共约127秒后才彻底放弃。这就是为什么很多“连接超时”的应用层错误要等很久才报出来——理论上一分钟多实际体验非常煎熬。排查时看到多个SYN重传包时间间隔分别是1秒、2秒、4秒、8秒这样退避就说明是本机的TCP栈在正常重试问题在网络路径或对端。2.2 连接慢、响应慢重点抓重传和ACK如果说连接失败是“看得见的病”那重传就是“慢性病”它让你觉得系统“卡”但又没到完全不可用的程度。TCP的可靠性靠的是确认机制发送方发了数据要等接收方回ACK才放心超过重传超时RTO没等到ACK就重新发一遍。重传本身是设计的一部分偶尔一两个重传不稀奇但如果重传比例偏高就说明网络上存在丢包或者接收方处理不过来。抓包时重传包在tcpdump的终端输出里通常会有tcp retransmission的注释写文件后用tcpdump -r查看也有在Wireshark里则是一个单独的标记。正常流程中数据包的时间应该是平滑递增的一旦你看到大量数据包的时间戳“倒退”到之前的值同时伴随“tcp retransmission”丢包问题就实锤了。除了重传还有一个现象叫DUP ACK重复确认。接收方发现丢了一个包但后续的包还在继续到达它会不停地发送“我期待的是第N号包”的ACK这就是DUP ACK。发送方连续收到3个DUP ACK就触发快速重传不等RTO超时。看到DUP ACK能进一步缩小范围丢包发生在中间某个具体的位置而且极可能是单方向的路径问题。2.3 连接被重置RST包意味着什么RST包是TCP协议里最“粗暴”的终止信号收到它意味着连接立刻死亡缓冲区里所有未确认的数据直接作废。这和FIN不一样FIN是“好聚好散”大家优雅地挥手告别RST是“话不投机半句多”直接断。抓包时看到RST首先要做的是分清到底是谁发的、发RST那一刻连接处于什么状态。产生RST的场景非常多但常见的就是这么几类目的端口没有进程在监听内核直接回RST连接请求被防火墙策略拦截后模拟TCP栈发RST比如某些云安全组对端进程崩溃重启连接状态丢失本机TCP超时清理半开连接应用层主动调用SO_LINGER强制关闭。排查RST时我通常会在两端同时抓包做时间对齐看谁先发RST。谁先发谁就是“主动翻脸”的一方问题大概率就在它身上。然后再结合那台机器上的进程状态和内核日志缩小范围。3. 实战复盘一次三次握手超时的完整定位3.1 先收集信息再动手别上来就抓很多人的习惯是接到工单就立刻打开tcpdump然后发现抓了一堆不知道是啥的包分析起来更懵。我的习惯是先花两分钟收集信息本机IP是什么对端IP和端口是什么当前这条连接在内核里的状态是什么样的。用到的命令很简单# 查看当前与目标地址相关的连接状态 ss -tn state established | grep 10.0.0.5 ss -tn state syn-sent | grep 10.0.0.5 # 查看本机路由确认数据从哪张网卡出去 ip route get 10.0.0.5ip route get这条命令的价值很大它不光告诉你走哪张网卡还能告诉你源地址选的是哪个。如果你机器上有多个IP而应用的源地址是另一个路由策略会直接影响抓包的过滤条件。信息收集完毕再决定在哪里抓、抓什么。3.2 抓包命令怎么设计一次讲透假设要排查本机192.168.1.10访问对端10.0.0.5的8080端口超时我的抓包命令是这样的tcpdump -i any -nn -s 0 host 10.0.0.5 and port 8080 and tcp -w /tmp/syn_timeout.pcap注意我把过滤条件用单引号包起来里面用了tcp关键字来限定协议。如果你不写tcp那么DNS、UDP等其他协议的流量只要满足host和port条件都会被抓进来。端口8080如果恰好是HTTP服务的你可能会抓到大量TLS握手和应用数据噪音很大。分析连接建立问题时可以再缩窄到只抓带有SYN标志的包tcpdump -i any -nn -s 0 tcp[13] 2 ! 0 and host 10.0.0.5 and port 8080 -w /tmp/syn_timeout.pcap这里tcp[13]是取TCP头第14个字节偏移13从0开始算——也就是标志位所在的那个字节。 2是对SYN位做按位与。这里要特别提醒这个过滤会同时抓到SYN和SYN-ACK但没关系我们就是要同时看这两个方向的包才能判断问题在哪一侧。抓完包用如下命令回放查看tcpdump -nn -r /tmp/syn_timeout.pcap -tttt加-tttt是为了显示完整的日期时间配合抓包时长能判断重传节奏。如果你的包里有这样的输出序列21:00:01.000001 IP 192.168.1.10.52001 10.0.0.5.8080: Flags [S], seq 1000 21:00:02.000001 IP 192.168.1.10.52001 10.0.0.5.8080: Flags [S], seq 1000 21:00:04.000001 IP 192.168.1.10.52001 10.0.0.5.8080: Flags [S], seq 1000三次SYN间隔约1秒、2秒指数退避但没有任何一个SYN-ACK回来。这基本可以确定SYN出得去但回包进不来要么是中间链路单向丢包要么是对端防火墙拦了入方向的SYN没有回包或者对端根本没这个服务但防火墙没有回RST而是直接丢弃。下一步动作就是去对端抓包验证如果对端也抓不到SYN那问题就在更深层的位置。3.3 从握手包推断RTT和丢包位置如果SYN-ACK能回来但ACK发出去后连接还是建立不起来情况就更有意思了。这种问题往往表现为SYN和SYN-ACK都成功交换了但应用层迟迟没有数据然后某个时刻收到RST。我在一次跨机房联调中就遇到过类似的问题本机三个端口分别连三台服务器其中两台正常一台总是连接建立后立刻断。抓包看三次握手完全正常问题出在第四次包——客户端发完ACK后服务端立刻回了RST。遇到这种情况千万不要在客户端抓包上死磕。直接去服务端抓会发现服务端的内核状态根本没有这个连接回RST是因为服务端进程根本没收到连接。当时进一步查发现服务端配置的内部负载均衡只在后端节点上放了90端口而我们的应用连接的是8080服务端进程监听的是另一个端口连接当然被内核拒绝。这就是典型的“客户端看得见握手服务端看不见连接”的两层认知差只有双端对比抓包才能快速定位。4. 实战复盘重传与响应缓慢问题的定位技巧4.1 用tcpdump直接观察重传节奏响应慢、接口超时这类问题很多情况下不是代码逻辑复杂而是网络在反复重传。判断方法很简单抓包时加上-tttt观察数据包时间如果你看到某个应用数据包发送后隔了一段时间又发了一遍相同序列号的包就说明发生了重传。tcpdump在读取抓包文件时如果识别到重传还会在包注释里直接标出来。重传的关键不是“看到它”而是“看到它之前发生了什么”。所以我在抓包时通常会把TCP头部选项也抓全用-vv参数增加输出细节。这样能看到接收方通告的窗口大小Window以及时间戳选项。窗口突然变成0说明接收方缓存满了发送方再发数据就是徒劳只能等窗口更新。时间戳选项则能精确计算数据包单向延迟如果时间戳显示一个包的发送时间和接收时间差了几百毫秒而你确认走的是内网那基本是链路质量出了问题。4.2 重传问题的常见根因排序根据我的经验重传问题的根因按出现频率排大约是物理链路丢包、中间防火墙安全策略丢包、TCP缓冲区太小、接收方处理不过来、网卡异常或驱动bug。物理链路丢包最好查看对端交换机端口的CRC错误计数就行防火墙策略丢包最气人因为它不做回应数据包就像进了黑洞只能靠两端对比抓包定位。比较隐蔽的是MTU问题导致的重传。TCP在握手时会协商MSS最大报文段大小如果中间链路MTU比两端协商出来的MSS小大包就会被丢弃。表现出来就是小包通信正常一传大数据就断然后重传。查这个问题我习惯抓包时指定-s 0抓全包然后在Wireshark里看[Packet size limited during capture]之类的标记。用tcpdump也能看如果同一份payload你看到一个包发了两次第二次报长度明显不同大概率是分片和重组的问题。# 查看系统当前的TCP重传统计实际是全局累计值 netstat -s | grep -i retrans这个命令输出的重传计数是系统开机以来的汇总值。排查时先记一个基准值过几分钟再看一次如果增长很快就说明系统整体在经历丢包而不是某一个连接的问题。诊断范围一下子就打开了。4.3 感受一下把抓包结果和系统状态放在一起看单纯抓包能告诉你“网络丢了包”但解释不了“为什么丢包”。所以我的习惯是抓包的同时记录系统的网络栈状态用到的命令不多# 队列长度和丢包统计 ss -s netstat -iss -s输出里有TCP的当前连接数和各种计时器信息。netstat -i能直接看到网卡层的收发包错误数、丢包数和溢出数。如果一块网卡的RX-ERR或RX-DRP列长期大于0问题就不在网络而在本机网卡或中断处理上。我遇到过一次高并发场景下大量TCP重传抓包怎么看都是对端没回ACK但查来查去发现是本机网卡多队列没有开启CPU软中断全部堆在一个核上处理不过来了导致收包延迟和丢包。这个案例当时如果不看netstat -i光分析包可能要浪费很多时间。5. 实战复盘RST连接重置问题的排查实录5.1 用过滤快速锁定RST包RST包的出现通常是“突然死亡”而且它本身往往只占用一个包的数据量混在大量正常流量里非常容易漏看。所以我排查RST时第一件事就是把RST包单独过滤出来tcpdump -i any -nn tcp[13] 4 ! 0 -w /tmp/rst.pcaptcp[13] 4就是RST标志位。抓到后查看来源IP和端口基本就能判断出是谁在“主动发难”。这里有个小细节TCP头发送方在设置RST时往往也会带上ACK标志RST-ACK所以不能简单靠“只有RST没有ACK”来认定而是要综合连接当时的序列号和确认号来分析。收到RST的一瞬间连接里已经传输的数据全部作废所以应用层可能同时报“Connection reset by peer”。这句话网上很多人解读为“对端主动断开”其实不准确。它只说明你收到了RST但谁发的、为什么发必须结合抓包和两端状态才能确定。5.2 双端抓包确认谁先翻脸排查RST的黄金法则是双端同时抓。只有看到时间线你才能回答“谁的RST先出现”这个问题。如果是服务端先发RST而客户端根本没做什么异常操作那就是服务端的问题。如果客户端先发RST那就得回头看客户端应用逻辑。这里有一个很典型的坑就是半连接回收问题。一台服务器上可能有海量短连接关闭内核为了回收资源会定期清理TCP半开连接。如果清理的时候恰好有客户端在这个连接上发了新数据内核就直接回一个RST。这类问题在抓包上表现非常诡异发送方认为自己还在发送数据接收方已经忘了这个连接。解决方向通常不是抓包能搞定的而是要看应用是否用了连接池复用空闲连接或者调整内核的tcp_keepalive_time参数。5.3 RST问题的几个典型场景速查分享一下我工作中排查RST故障的频率清单供你参考场景抓包特征常见根因端口未监听SYN进来立刻回RST没有SYN-ACK进程没启动或监听在别的端口防火墙拦截后回RSTSYN进来后RST来源看起来像对端安全组/防火墙策略伪装内核行为连接空闲超时被回收长时间无数据后来一个数据触发RSTkeepalive配置不当或负载均衡空闲超时服务端进程崩溃已有连接上收到RST时间点和进程崩溃一致进程异常退出内核清理连接TIME_WAIT过多导致新建连接失败新SYN收到RST老连接TIME_WAIT堆积四元组复用冲突或tcp_tw_reuse配置不当每次我遇到RST第一件事都是看时间点是否和某个操作吻合是不是刚发布过代码是不是负载均衡刚做过健康检查是不是防火墙刚改了策略RST不像超时那样有模糊的等待空间它是精确到毫秒的所以时间点是破案的第一线索。6. tcpdump使用中的常见坑与提效技巧6.1 抓包时的性能顾虑怎么把影响降到最低生产环境抓包大部分人的顾虑是“会不会增加延迟”。tcpdump本身只复制包理论上单包占用的CPU很低但前提是你过滤得当。我有一次在流量很大的网关上抓包因为没有加port限制直接抓了整个网卡的所有流量结果tcpdump进程CPU直接飙到200%。后来加了精确的hostport过滤CPU降到个位数。另外记住一个大原则线上抓包别追求“全”追求“准”。宁可用-w落盘、用-c限制包数量也不要让终端刷屏——终端输出本身比抓包更费CPU。如果必须长时间抓包建议配合strace确认tcpdump进程没有异常行为或者干脆用snaplen只抓包头比如-s 96分析连接级问题足够了占用的IO会小很多。6.2 抓包文件的保存和分析习惯tcpdump写文件时默认是不带缓冲的这意味着如果你用CtrlC中断文件末尾可能缺几个包。这不算bug但会影响分析。解决方法是抓包结束后等一两秒再中断或者用timeout命令自然终止。还有一个实用技巧抓包文件最好用pcap格式因为无论后续用Wireshark、tcpdump还是tshark去读都可以格式兼容性最好。分析阶段我推荐一个习惯先用tcpdump -r快速过一遍确认包的数量、标志位方向、是否有重传标注再用Wireshark做图形化深挖。Wireshark里可以直接看TCP流图、计算RTT、看Expert Information提示很多tcpdump终端上不容易看出来的问题图形界面里一目了然。但千万不要本末倒置——图形工具只能帮你确认现象定位根因还是要回到系统状态和业务日志上。6.3 更精准的过滤指定标志位组合过滤表达式里最容易出错的场景已经提过了纯SYN和SYN-ACK的区分。我平时有一个习惯对于需要精确匹配标志位的场景宁可多敲几个字符也要写成完整的位掩码比较。比如# 只看纯SYN包SYN1 且 ACK0 tcpdump -nn tcp[13] (tcp-syn|tcp-ack) tcp-syn # 只看纯ACK包ACK1 且 SYN0 且 FIN0 且 RST0 tcpdump -nn tcp[13] (tcp-syn|tcp-fin|tcp-rst) tcp-ack这里tcp-syn、tcp-ack都是tcpdump内置的宏对应标志位的数值。用括号把多个标志位包起来做位运算再跟目标值比较能精确匹配组合。这种方法比单独 tcp-syn ! 0更可靠因为在分析建连异常时把SYN-ACK误当成SYN会让你得出完全错误的结论。7. 写在最后把抓包能力沉淀成自己的诊断习惯按照我个人的经验学会tcpdump语法只是一星期的事真正值钱的是在一次次排障中积累的“抓包直觉”。你看到一堆包的时候能不能最快反应出这是哪一层的谁在做什么、正常流程应该是什么样、现在偏离了什么——这个直觉没法靠参数手册培养只能靠多抓、多看、多对比双端状态。最后分享一个我一直沿用的工作习惯每次抓包排障我都会把系统当时的连接状态ss -s、TCP统计netstat -s、网卡错误统计netstat -i和抓包文件放在一起作为一个完整的“故障现场”保存下来。等故障修复之后再回来翻一遍对比正常状态下的抓包有什么不同。几次下来你对TCP协议的理解就会从“课本上的三次握手”变成“能摸到心跳的真实网络”。希望你也能在下次遇到网络怪问题时先想到打开tcpdump看一眼——它不会说话但数据包从来不说谎。