抓包不是“打开 Wireshark 看一眼”,而是一套从定位抓包点 → 解密 → 逐层读协议 → 归因的方法论。Web 排障的难点在于三层协议叠在一起:TCP 决定“通不通、快不快”,TLS 决定“握没握上、证不证书”,HTTP 决定“语义对不对”。下面按这个顺序给出可执行的命令与代码。
抓包第一原则是贴着故障侧抓。客户端慢抓客户端网卡,服务端处理慢抓回环,链路问题抓网关。容器里抓 eth0,注意 sidecar 会改变流量路径。抓之前先写 BPF 过滤,避免 pcap 被无关流量撑爆。
# 只抓目标主机与端口,全量载荷,带绝对时间
tcpdump -i eth0 -s 0 -nn -tttt \
'host 10.0.2.15 and (port 80 or port 443)' -w web.pcap
# 只保留有载荷的 TCP 包(过滤纯 ACK),直接看明文请求行
tcpdump -i eth0 -A -s 0 \
'tcp port 80 and (((ip[2:2]-((ip[0]&0xf)<<2))-((tcp[12]&0xf0)>>2))!=0)'第二条表达式用 IP 总长减 IP 头减 TCP 头算载荷长度,非零才打印,日志立刻清爽。
TLS 让内容不可见,但排障必须解密。生产可用的合法路径是客户端导出会话密钥(SSLKEYLOGFILE),Wireshark 加载后自动解密,不涉及中间人。
export SSLKEYLOGFILE=/tmp/keys.log
curl -v https://api.example.com/health -o /dev/null
# Wireshark: Preferences → Protocols → TLS → (Pre)-Master-Secret log 填 /tmp/keys.log只做单次请求排查时,用 mitmproxy 插件更方便,还能顺手改写请求头注入 traceId:
# addon.py → mitmproxy -s addon.py
from mitmproxy import http
def request(flow: http.HTTPFlow) -> None:
if "api.example.com" not in flow.request.pretty_host:
return
flow.request.headers["X-Capture-Trace"] = "cap-001"
print(f"[REQ] {flow.request.method} {flow.request.path}")
print(" host:", flow.request.headers.get("host"))
def response(flow: http.HTTPFlow) -> None:
if flow.response.status_code >= 400:
print(f"[{flow.response.status_code}] {flow.request.path}")
print(flow.response.text[:300])解密后第一件事是核对 ClientHello 的 SNI 与 HTTP 的 Host 是否一致——不一致会导致 CDN 返回错误证书或错误站点,是“证书错误”类工单的头号原因。
明文 HTTP 要读三处:请求行/响应行的语义、Content-Length 与 Transfer-Encoding 是否冲突、Set-Cookie 的属性组合。
用 Wireshark 显示过滤器快速收敛:http.request.method == "POST"、http.response.code >= 500、http.content_type contains "json"。批量分析用 scapy 还原事务:
from scapy.all import rdpcap, TCP, Raw
def http_requests(path):
out = []
for pkt in rdpcap(path):
if TCP not in pkt or Raw not in pkt:
continue
data = pkt[Raw].load
if data.startswith((b"GET", b"POST", b"PUT", b"DELETE")):
head = data.split(b"\r\n\r\n")[0].decode("latin1")
out.append({"ts": float(pkt.time), "head": head})
return out
for r in http_requests("web.pcap")[:5]:
print("---", r["ts"])
print(r["head"][:400])Content-Length 与 Transfer-Encoding: chunked 同时出现是协议违规,代理行为不确定,直接导致响应截断或挂起。
HTTP/2 是二进制分帧,看不到请求行。用 tshark 直接按流导出帧,关注 :method、:path、:status 伪头,以及 RST_STREAM 错误码——RST_STREAM 常意味着服务端主动取消或流控异常。
tshark -r web.pcap -Y "http2" \
-T fields -e http2.streamid -e http2.headers.method \
-e http2.headers.path -e http2.headers.status -e http2.type多路复用下一条 TCP 连接承载多个流,读包必须同时看 stream id 与帧类型,否则会误判“请求丢失”。
握手阶段仍是 HTTP,看 Upgrade: websocket 与 Sec-WebSocket-Accept 是否正确回显;升级成功后转帧视图。抓包过滤器 websocket 直接定位。
const ws = new WebSocket("wss://api.example.com/ws");
ws.onopen = () => ws.send(JSON.stringify({ type: "sub", ch: "ticker" }));
ws.onmessage = e => console.log("frame:", e.data);常见问题是代理不支持长连接、心跳缺失被中间设备掐断——抓包看 Ping/Pong 帧间隔即可判断。
HTTP 慢,九成能在 TCP 层找到答案。用 tshark 统计重传与握手耗时:
tshark -r web.pcap -q -z io,stat,0 \
-z "io,stat,0,COUNT(tcp.analysis.retransmission)tcp.analysis.retransmission" \
-z "io,stat,0,AVG(tcp.time_delta)tcp.time_delta"判据速查:SYN → SYN/ACK 时间差即建连 RTT;重传间隔按 1s/2s/4s 翻倍是丢包;TCP ZeroWindow 是接收方应用读得太慢,问题在应用;TCP Window Full 叠加小载荷是发送窗口被卡。看到 RST 要确认是谁发的,网关 RST 常见于上游超时。
现象 | 抓包点 | 关键判据 |
|---|---|---|
首屏慢 | 客户端 | 建连 RTT、TLS 轮次、队头阻塞 |
偶发 502 | 网关 | 上游 RST、超时值、连接池耗尽 |
登录态丢失 | 双端 | Set-Cookie 属性、SNI/Host 不一致 |
上传中断 | 双向 | 重传密度、零窗口、PMTU 黑洞 |
抓包排障的本质是分层归因:HTTP 看语义,TLS 看握手与证书,TCP 看时序与窗口。固定变量(同一时间窗、同一 traceId、同一版本),配好 keylog 解密与 tshark 统计,任何“感觉慢”都能收敛到一行可复现的协议事件——这就是抓包的价值。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。