ARTICLE DETAIL

资讯详情

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

tcpdump抓包实战:从TCP三次握手到重传问题排查

tcpdump抓包实战:从TCP三次握手到重传问题排查 前一晚还在盯着监控面板,第二天工单就来了:内网仓库推送镜像老是卡住,偶尔直接报dial tcp 192.168.1.100:443: connect: i/o timeout。你第一反应多半是“网络有问题”,但网络到底坏在哪,只靠ping和telnet往往说不清楚。我自己用 tcpdump 排查 TCP 数据包问题的次数,比用其它网络工具加起来都多。它不是万能的,但几乎所有 TCP 层面的疑难杂症,最后都要靠它还原现场。这篇文章想把整套方法完整写出来——从动手抓包前的思路准备,到读懂 tcpdump 输出里的三次握手,再到握手异常、重传、dup ack、乱序这些传输层症状怎么判读,最后用一个推送服务超时的真实案例走一遍完整链路。不管你是刚入门的运维、写网络程序的开发,还是偶尔要处理网络问题的测试,这套思路应该都能直接用在你的排查里。1. 动手抓包前,先把这三件事定下来1.1 明确要诊断哪个阶段TCP 连接从生到死,大致分三个阶段:建连、传输、断连。不同的症状对应不同的阶段,先判断再看包,才不会抓了半天不知道看什么。连接建立慢、连不上、超时:重点看三次握手过程,也就是 SYN、SYN-ACK、ACK 这三条报文。传输过程卡顿、速度上不去、页面转圈:重点看 seq、ack 的推进,以及有没有重传、乱序、丢包。连接无故断开、发送后立刻被掐断:重点看 FIN 和 RST 的时序,谁先发的断开请求。举个例子,如果你在排查一个“服务偶尔响应慢”的问题,结果包里全是握手阶段的重传,那问题大概率出在连接建立环节,而不是业务逻辑。这个前置判断能帮你避免被大量无关报文淹没。1.2 确定抓包点:客户端、服务端还是中间链路同一个 TCP 连接,在客户端抓到的包和在服务端抓到的包,几乎永远是两回事。这也是 tcpdump 诊断的核心思路:对比两侧视角,才能把问题压缩到某个区间。在客户端抓包:能看到“发出去什么、收回来什么”。在服务端抓包:能看到“实际收到什么、回应了什么”。中间设备(防火墙、负载均衡、安全组)上往往不方便抓包,但恰恰是问题高发区。我习惯的做法是,条件允许时在客户端和服务端同时抓包,时间尽量对得上。如果只抓一端,另一半只能靠系统日志和ss -tlnp这种命令去猜,效率会低很多。后面案例里我会重点演示这种双侧抓包的对比法。1.3 过滤表达式不是越复杂越好,但要足够聚焦tcpdump 的过滤用的是 BPF(Berkeley Packet Filter)语法。我日常最常用的组合是:sudo tcpdump -i eth0 -nn -s 0 -w /tmp/debug.pcap host 192.168.1.100 and tcp port 443这里几个参数解释一下:-i eth0:指定网卡。机器有多块网卡时一定写清楚,否则可能抓错口。-nn:不做域名解析,也不把端口翻译成服务名。这样输出干净,速度也更快。-s 0:抓取完整包,不截断。分析应用层数据时必须有这个,否则-w存下来的包不完整。-w /tmp/debug.pcap:存成 pcap 文件,这是事后分析的关键动作。host和tcp port:过滤源或目的地址、端口。如果一个连接涉及双方不同端口(比如客户端用 50000 连服务端 443),可以只写host不写port,或者用port 443 or port 50000。注意一点:在线直接看 tcpdump 刷屏,在流量大时终端会卡死。这时候用-c 200限制抓 200 个包就停,或者干脆一直-w写文件,等出问题再按 Ctrl-C 中断。我一个习惯是:临时看现象用-c,准备正经分析一律-w。2. tcpdump 输出逐字段拆解:先看清一次正常握手2.1 一行报文里到底藏了什么先看一条典型的 tcpdump 输出:10:33:42.123456 IP 192.168.1.10.55678 192.168.1.100.443: Flags [S], seq 4289641234, win 64240, options [mss 1460,sackOK,TS val 1521138372 ecr 0,nop,wscale 7], length 0逐段拆开看:10:33:42.123456:抓包时间。没有日期,跨天分析最好加-tttt显示完整年月日。192.168.1.10.55678 192.168.1.100.443:源地址源端口 目的地址目的端口。这里客户端是 192.168.1.10 的 55678 端口,服务端是 443。Flags [S]:TCP 控制标志。S代表 SYN,也就是三次握手的第一步。seq 4289641234:序列号。tcpdump 默认显示相对序列号,加上-S参数才显示绝对序列号。分析重传时要能区分这两者,否则你会觉得两个包的 seq 不一样,其实它们指向同一段数据。win 64240:接收窗口大小,也就是接收方还能收多少字节。注意这个值有时候是经过窗口缩放(wscale)处理前的“裸值”,并不等于真实可用窗口。options [mss 1460,sackOK,TS val ...,ecr 0,nop,wscale 7]:TCP 选项。mss 通告最大分段大小,sackOK 表示支持选择性确认,TS val/ecr 是时间戳选项,wscale 7 表示窗口缩放因子是 7。这些选项在排查 MTU、性能问题时非常关键。length 0:载荷长度。握手报文都不带数据,所以是 0。2.2 Flags 缩写对照tcpdump 会用缩写组合表示 TCP 标志,新手最容易在这里懵。我给一张速查表:缩写含义典型场景SSYN发起连接请求S.SYN-ACK服务端同意建立连接.ACK确认收到数据,纯 ACK 不带载荷P.PUSH ACK携带应用层数据并马上推送F.FIN ACK主动关闭连接R/R.RST / RSTACK强制断开连接或拒绝连接FP.FIN PUSH ACK关闭前发送最后一段数据抓包时看到[S]别以为只有一个标志,这只是缩写。真正要看的是包的双向配合:谁发 SYN,谁回 SYN-ACK,后续 ACK 有没有跟上。2.3 正常握手的完整画面正常建立连接,抓包应该看到以下三行(源端口我换一下更贴近实际):10:33:42.123456 IP 192.168.1.10.55678 192.168.1.100.443: Flags [S], seq 4289641234, win 64240, options [mss 1460,sackOK,TS val 1521138372 ecr 0,nop,wscale 7], length 0 10:33:42.223456 IP 192.168.1.100.443 192.168.1.10.55678: Flags [S.], seq 1844674407, ack 4289641235, win 65160, options [mss 1460,sackOK,TS val 900000000 ecr 1521138372,nop,wscale 7], length 0 10:33:42.224456 IP 192.168.1.10.55678 192.168.1.100.443: Flags [.], ack 1844674408, win 502, options [nop,nop,TS val 1521138446 ecr 900000000], length 0第一行客户端发 SYN,第二行服务端回 SYN-ACK(注意它带上了ack,这是对 SYN 的确认),第三行客户端回 ACK。三次握手完成,之后才是真正的应用层数据。看到这条时间链路,基本可以断定 TCP 建连正常。如果只看到第一行,后面全没了,那就是 SYN 发出去了石沉大海。如果看到前两行,第三行丢了,那通常是客户端侧没收到 SYN-ACK,或者收到后回 ACK 被中间设备挡了。这是最基础的判断,但无数问题就是靠这一步定级的。3. 握手失败时,tcpdump 给的是什么“物证”3.1 只看到 SYN 反复重传:问题还在前几跳最典型的失败画面是:客户端每隔约 1 秒、2 秒、4 秒重复发出同样的 SYN,间隔指数增长,系统最多重试几次后放弃并报超时。这时去服务端抓包,如果什么 SYN 都看不到,基本可以断定是中间链路的问题——防火墙丢包、安全组没放行、路由黑洞,都有可能。举个例子,我遇到过内网安全组只放行了 80 端口,忘记放行 8443,结果客户端一直重传 SYN,服务端一侧却完全没有 SYN 到达。后来把安全组补上,抓包立刻出现正常的[S.]回复。这里的关键结论是:客户端能看到重传,不代表包真的离开本地,更不代表对方能收到。3.2 服务端有 SYN 但没回 SYN-ACK:查监听和队列还有一种情况是,在服务端抓包能看到客户端发来的 SYN,但服务端就是不回 SYN-ACK。这种问题大概率临时不在网络层,而在服务端的 TCP 栈或应用状态:服务端口根本没有进程监听:内核通常会直接回 RST,但某些防火墙会帮你把 RST 也拦掉。监听队列(backlog)满了:新 SYN 排队排不上,被内核直接丢弃。这时配合ss -tlnp看Send-Q(当前等待应用 accept 的连接数),如果数值接近Backlog上限,说明应用层 accept 太慢。内核参数net.ipv4.tcp_syncookies关闭且半连接队列打满:服务端也可能静默丢 SYN。我自己的排查顺序是:先在服务端看ss -tlnp | grep 443,确认监听存在;再看netstat -s里SYNs to LISTEN sockets dropped这类计数有没有暴涨;最后回头对照抓包结果,判断是丢在防火墙还是丢在内核队列。3.3 收到 RST:端口没监听,还是主动拒绝有时抓包会看到一方直接回 RST,连接立刻失败。RST 的意思是“我拒绝你,别再来”。区分两种常见原因:端口无进程监听:内核直接回 RST,抓包表现为 SYN 过来后立刻Flags [R.]。应用层主动关闭:例如服务端 accept 之后发现来源 IP 不在白名单,主动close()或设置 SO_LINGER 后强制断开,抓包看到的是建连成功后立刻 RST,而不是 FIN。这里有个小技巧:如果建连阶段 SYN 之后立刻 RST,优先查端口监听和防火墙;如果数据交互一两秒后才 RST,大概率是应用层策略或服务端异常退出。前者网络问题多,后者代码问题多,方向别搞反。3.4 SYN 泛洪在抓包里的长相安全相关的场景里,SYN 泛洪的抓包特征非常明显:短时间内大量不同源 IP 的 SYN 发向同一端口,连接永远停留在半开状态,服务端大量SYN_RECV堆积。用 tcpdump 过滤时,可以只抓 SYN 包:sudo tcpdump -i eth0 -nn tcp[tcpflags] tcp-syn ! 0 and tcp[tcpflags] tcp-ack 0这个表达式抓的是纯 SYN(不含 ACK)的包。如果统计到几十秒内有成千上万个不同源地址的纯 SYN,基本可以判断是扫描或泛洪,而不是正常的业务连入。防御层面的事不展开,但抓包定位这类问题,这个过滤写法可以直接抄。4. 重传、dup ack 与乱序:传输阶段的三大典型症状4.1 先搞懂 seq 和 ack 是怎么配合的三次握手建立之后,数据开始流动。TCP 对每个字节编序号,发送方用seq表示“这段数据从哪个字节号开始”,接收方用ack表示“我已收到到哪个字节,下一个请从这个字节号发过来”。简单说,seq是发方的进度,ack是收方的反馈。举个例子,如果发送方发了 seq 100、长度 100 字节的一段数据,接收方正确收到后,回包的ack应该是 200,意思是“100 到 199 我都拿到了,下一个我要 200”。tcpdump 默认显示相对序号,所以你常常看到 seq 从 0 开始。为了看清绝对位置,抓包时建议加-S,分析重传时尤其有用。4.2 超时重传和快速重传的抓包区别TCP 的可靠性靠重传保证,但重传出现得太频繁,就是网络或系统的病征。抓包时能看到两种典型形态:超时重传:发送方发完数据后迟迟收不到 ACK,等 RTO(重传超时)到期后重新发送同一段数据。抓包表现是同一个 seq 出现了两次,间隔通常在 1 秒、2 秒这样有规律地变长。快速重传:接收方收到了乱序或缺失的数据,连续重复 ACK 同一个ack号,发送方收到 3 次 dup ack(重复确认)后,不等超时立刻重传。抓包表现是有一串相同的 ACK 包,随后紧跟一个重发的相同 seq。这里要特别提醒:看到重传不要第一反应就是“线路丢包”。我处理过一个案例,应用层自己把发送缓冲区设置得过小,数据积压导致超时,重传非常多,但底层网络一点包都没丢。重传只是症状,丢包、拥塞、对端处理慢、本地发送 buffer 不足都可能引发。4.3 dup ack 并不是丢包的“实锤”dup ack 在 Wireshark 里会被标成TCP Dup ACK,但它在抓包里频繁出现时,并不一定代表真的丢了包。网络乱序也会导致接收方发出重复 ACK,因为接收方等的某个中间段还没到,后面到的数据只能缓存起来,不断重复 ACK 催对方补发。区分的方法很简单:看后续有没有紧接着的乱序重传包。如果 dup ack 之后发送方确实重传了一段之前没到过的数据,那就是真丢包;如果所有数据其实都到齐了,只是顺序乱了,那大概率是负载均衡的多路径转发、网卡多队列处理差异之类的顺序问题,定性会完全不同。Wireshark 的 Expert Info 里会区分Duplicate ACK和Out-of-Order,分析时要先看这些标记,别把乱序硬说成丢包。4.4 用抓包估算重传率,比凭感觉靠谱有了 pcap 文件,可以用 tcpdump 直接做初步统计。比如我想看一个文件里所有 TCP 重传相关的包有多少:sudo tcpdump -r /tmp/debug.pcap -nn tcp[tcpflags] tcp-syn ! 0 | wc -l不过更直观的是用 Wireshark 的 Expert Info 自动标注Retransmission、Dup ACK、Out-of-Order。打开 pcap 后点 “Analyze - Expert Info”,里面会有分类统计,能快速看整体健康状况。我一般以重传占整个报文的比例来粗判:如果超过 1%,说明链路质量已经值得警惕;超过 5%,基本能感觉到明显卡顿。这个阈值不是标准,但很好用。4.5 排查重传根因时,要顺带看主机侧指标抓包只能告诉你“发生了什么”,不能直接告诉你“为什么发生”。配合系统命令,能更准地定位根因:ss -ti可以看当前 socket 的拥塞状态、RTO、重传次数、cwnd 等,非常直观。ethtool -S eth0 | grep -E rx_dropped|rx_missed|tx_errors看网卡丢包计数。如果网卡 ring buffer 满导致丢包,重传数据会很大,但网线质量可能完全正常。ip -s link show eth0看接口层面的错误包数。网卡开启了 TSO/GRO offload 时,抓到的包可能和实际线上的包长相不一致。怀疑性能问题时,可以临时用ethtool -K eth0 tso off gro off gso off关闭 offload 再抓一次对照,排除驱动和硬件处理的问题。这些指标和 tcpdump 对照着看,很快能把范围从“网络层”收敛到“主机层”或“应用层”。5. 一次推送服务超时的完整排查:从抓包到定位5.1 现象:镜像推送反复超时先说说那个典型的工单。内网 Harbor 仓库地址是192.168.1.100,端口 443,客户端执行docker push时频繁报:dial tcp 192.168.1.100:443: connect: i/o timeout报错信息里“connect”出现在最前面,说明问题大概率卡在 TCP 建连阶段,连 TLS 握手都没开始。如果卡在传输阶段,报错通常是connection reset by peer或write: broken pipe这种。这一步判断,已经能帮我们锁定去看握手的包。5.2 第一步:客户端抓包,确认 SYN 的去向在客户端执行:sudo tcpdump -i eth0 -nn -s 0 -c 200 host 192.168.1.100 and tcp port 443 -w /tmp/push_timeout.pcap抓完看结果,典型的失败画面是:14:00:01.000001 IP 192.168.1.10.55001 192.168.1.100.443: Flags [S], seq 1000, win 64240, options [mss 1460,sackOK,TS val 111 ecr 0,nop,wscale 7], length 0 14:00:02.000002 IP 192.168.1.10.55001 192.168.1.100.443: Flags [S], seq 1000, win 64240, options [mss 1460,sackOK,TS val 211 ecr 0,nop,wscale 7], length 0 14:00:04.000003 IP 192.168.1.10.55001 192.168.1.100.443: Flags [S], seq 1000, win 64240, options [mss 1460,sackOK,TS val 411 ecr 0,nop,wscale 7], length 0同一个 seq 的重传间隔 1 秒、2 秒地翻倍,但始终没有 SYN-ACK 回来。结论已经清晰:SYN 发出去了,但对方没有任何回应。CLIENT 侧看见的全是 SYN,没有 SYN-ACK,说明问题在“客户端到服务端这条路径的某个地方”。5.3 第二步:服务端抓包,验证 SYN 有没有到达接着在 Harbor 仓库所在主机上抓包:sudo tcpdump -i any -nn -s 0 -c 100 host 192.168.1.10 and tcp port 443 -w /tmp/server_side.pcap如果服务端也完全看不到 SYN,那就是中间链路把包丢了——最常见的是防火墙或云安全组的入站规则只放行了特定端口,而 443 恰好被漏掉。如果服务端能看到 SYN 却不回 SYN-ACK,那就要进入下一步查服务本身。现实中的情况往往是:中间安全组没放行 443。因为 Docker 客户端访问 Harbor 走 HTTPS,而安全组模板里只开了 80 或 22,把 443 漏了。补上规则之后,两侧同时抓包,立刻能看到完整的[S]-[S.]-[.]三步握手。5.4 第三步:如果收到 SYN 却无回应,查监听和系统层假设服务端抓到了 SYN,但就是没回,优先级最高的检查是:ss -tlnp | grep 443如果端口没监听,那服务端内核会回 RST,而不是什么都不回。如果你既没看到 RST 也没看到 SYN-ACK,那要看:netstat -s | grep -i listen:SYN 丢弃计数是否在上涨。sysctl net.ipv4.tcp_syncookies:半连接队列保护是否开启。应用进程的 listen backlog:比如 Nginx 的backlog、Harbor 的accept能力,如果队列满,新 SYN 会被直接丢弃,表现和防火墙丢包几乎一样,但服务端的抓包已经告诉你,问题就在这台机器上。这一步凡是漏了,就很容易在中间链路上瞎猜半天。五层模型里每一层都有自己的“物理证据”,TCP 抓包就是在传输层找证据,但它也要和应用层状态结合起来看。5.5 另一种相关报错:端口无法暴露的问题热词里提到的error response from daemon: ports are not available: exposing port TCP 0.0.0.0是另一个容易混淆的场景。它通常发生在宿主机端口被占用,或内核临时端口范围耗尽时。用户执行docker run -p 8443:443报端口不可用,这时 tcpdump 抓回环或抓 eth0 都能看到RST:sudo tcpdump -i lo -nn -c 50 tcp port 8443如果本机有进程占用 8443,抓包会有 SYN 到达但立刻收到 RST 的痕迹,用ss -tlnp | grep 8443找到占用方即可。如果端口是空闲的但 Docker 报端口不可用,则要检查net.ipv4.ip_local_port_range是否被大量短连接耗尽,这个用cat /proc/sys/net/ipv4/ip_local_port_range看范围,用ss -tan统计连接数判断是否打满。这类问题虽然不在抓包的核心戏份里,但它是 tcpdump 诊断 TCP 端口问题时最常见到的“邻居问题”。6. 抓包数据的二次分析:从 pcap 文件到结论6.1 为什么在线刷屏不够,一定要留 pcaptcpdump 直接往终端打印,适合快速验证现象,但真到了出问题要回溯现场的时刻,线上流量早就过去了。一个 pcap 文件把抓包时间点的双向报文都存了下来,你可以在事后慢慢放大缩小时间轴、过滤不同标志位、对比两侧的包。所以我的原则很简单:抓包一律-w写文件,终端里只做快速看一眼的临时操作。另外,pcap 是明文的流量快照,里面可能带着业务数据、账号、密钥这样的敏感信息。我见过同事把一个带 token 的 pcap 发到协作群里,结果 token 泄露的尴尬事。发 pcap 给别人之前,一定先用 Wireshark 或tcprewrite脱敏,或者干脆自己先分析完再给结论。6.2 用 tcpdump 回放文件做初步筛选抓完包,如果身边一时没有 Wireshark,可以用 tcpdump 本身继续分析:# 只看 SYN 包 sudo tcpdump -r /tmp/debug.pcap -nn tcp[tcpflags] tcp-syn ! 0 # 只看 RST 包 sudo tcpdump -r /tmp/debug.pcap -nn tcp[tcpflags] tcp-rst ! 0 # 看某个端口的所有双向报文 sudo tcpdump -r /tmp/debug.pcap -nn tcp port 443回放文件的优势是快、不产生额外流量,配合-tttt还能看到完整的时间线。如果要对比多段间隔,可以用grep把时间戳统计出来,比如:sudo tcpdump -r /tmp/debug.pcap -nn | head -1 sudo tcpdump -r /tmp/debug.pcap -nn | tail -1首尾两个包的时间差,就是这个 pcap 覆盖的时长。我有一次排查“偶发卡顿”,就是用这种方式确定抓包窗口 4 个小时里只出现了 5 次重传,直接否定了“网络质量差”这个假设。6.3 Wireshark 里的几个高性价比功能pcap 文件用 Wireshark 打开后,别只盯着报文列表看,这几个功能最快能帮你把结论提炼出来:Expert Info:自动标注重传、dup ack、乱序、TCP zero window,一眼看到异常点在哪。Follow TCP Stream:把一次 TCP 连接里的应用层数据拼成完整对话,适合看 HTTP 请求和响应是否完整。TCP Stream Graph:Time-Sequence Graph 能看到序列号和时间的曲线,如果曲线出现回退,基本就是重传。Statistics - Conversations 里的 TCP 页,能按连接统计报文数、字节数、重传次数,找出最“病”的连接。还有一个小技巧:Wireshark 的tshark命令行工具可以直接导出统计值:tshark -r /tmp/debug.pcap -q -z io,stat,0,tcp.analysis.retransmission这条命令会列出每个时间段的 TCP 重传分布。比肉眼翻包高效得多。6.4 长时间抓包的几个实用技巧排查“偶发”问题通常要抓很久,几个经验供参考:用环形缓冲防止磁盘打满:sudo tcpdump -i eth0 -nn -s 0 -w /tmp/ring.pcap -G 3600 -W 24 tcp port 443每小时一个文件,最多保留 24 个文件(一天的量),新文件自动覆盖最老的。这个命令适合挂在一个终端里,后台不用管。抓包时同时记录系统时间偏差。多端对比时间线时,如果两端 NTP 不同步,时间戳对不上,结论容易跑偏。我一般先ntpdate -q ntp-server或chronyc tracking看一眼偏差,再开始抓包。抓包期间尽量保持负载可复现。很多“偶现问题”其实是在特定流量特征下才出现,你抓包时流量太小,症状不出来;流量大了又怕刷爆磁盘。用-G环形缓冲就是解决这对矛盾的办法。7. 几个我坚持了很久的抓包习惯最后把我自己的几个小习惯写出来,算是一线踩坑攒下的经验,供参考。第一个,别让 tcpdump 裸跑在终端里。除非只是看几秒钟,否则一定-w写文件,再配合-c或-G限制量和切分文件。否则流量一冲,终端直接卡死,包也丢没了。第二个,至少加-nn和-S。-nn避免 DNS 反查,-S显示绝对序列号。很多人分析半天发现 seq 对不上,就是因为 tcpdump 默认显示相对序号,重传对比时特别容易看花眼。第三个,抓包前先 ping 一下。别笑,真的遇到过有人在物理链路断开的情况下,抓了半天 eth0 什么都没抓到,然后一脸懵。基础连通性还是先验证一下,成本极低。第四个,分析时尽量做双侧对比。能同时在客户端和服务端抓,就两边的 pcap 一起看。只要有一侧的包缺失,就说明问题落在“发方-对端”的路径中某一点。单侧重传次数再多,也只能确认“它没收到 ACK”,确认不了“你的数据到底丢在了哪里”。第五个,遇到大包异常时,先怀疑 offload。网卡的 TSO/GRO 可能让 tcpdump 抓到的包和真实线上包在大小、分段方式上不一致。怀疑性能问题,先临时关掉 offload 再抓一次,很多“诡异 MTU 问题”其实都是 offload 在中间搅局。这篇文章把从“怎么想”到“怎么做”再到“怎么出结论”的全流程都过了一遍。tcpdump 本身只是一个给包拍照的工具,真正值钱的是你怎么读这张照片、怎么把它和系统状态连起来看。把这套流程走熟了,再遇到 TCP 数据包问题,你抓一次包基本就能压到两三个可能性里,剩下的就是挨个验证而已。
返回列表