抓包保存的是某个观察点、某段时间内采集到的报文。tcpdump 适合按条件采集和离线检查,Wireshark/TShark 适合协议字段、重组与时序分析。可靠结论来自“采集范围、协议事实、推断边界”三者一致,而不是某行被标红。
先回答的问题 对采集的影响
哪一段连接失败 客户端、代理入口、代理出口可能需要不同采集点
用 TCP 还是 UDP 只抓 TCP 443 会漏掉 QUIC
是否涉及 DNS 只按目标 IP 抓取可能看不到前面的解析失败
要查握手还是内容 决定是否提前开始、是否需要完整 snaplen 与解密材料
是否能完整保存 决定过滤范围、缓冲、文件轮换与停止条件
观察位置决定你能证明什么
同一次访问,不同观察点看到不同连接与报文形态
客户端
应用 / socket
代理或 NAT
终止连接或改写地址
后端服务
另一段连接
主机抓包可能位于卸载之前
代理后五元组可能变化
先记录接口、namespace、方向和时间基准
看不到包只能说明此处未采到;需要排除路径不同、过滤、截断和采集丢包。
Linux 宿主机、容器网络 namespace、veth、bridge、物理网卡和 loopback 不是同一个视角。-i any 覆盖的是当前 namespace 可采集的接口,不会自动进入所有容器。Linux 的 any 通常使用 cooked capture 链路类型,不应假定每个包都从标准以太网头开始解析。
经过 NAT 后地址和端口可能变化;经过反向代理则通常已是另一条 TCP 连接,不能沿用相同 tcp.stream 或五元组找后端。跨文件关联需结合时间、请求 ID、有效载荷特征和代理日志;各文件的 stream 编号只是本次分析结果中的索引。
两端同时抓包能缩小缺失发生的位置,但时钟未同步时,直接相减时间戳会产生错误的单向时延。微秒或纳秒精度的时间戳也不保证同等级测量准确度。
Capture filter 与 display filter 分别在哪生效
属性 Capture filter Display filter
常见使用处 tcpdump 表达式、TShark -f Wireshark 过滤栏、TShark -Y
作用对象 采集前筛选或离线读取筛选 已有报文经过解析后的字段
示例 tcp port 443tcp.port == 443
未匹配数据 实时采集时通常不写入捕获文件 原文件仍保留,可换条件重新分析
能力边界 依赖链路类型、偏移与编译器支持 依赖 dissector、重组和可见明文
实时采集中,libpcap 可在支持的平台将过滤程序交给内核,减少送入用户态的包;读取离线文件时也能使用相同表达式,但并不是“所有 BPF 过滤永远发生在内核”。没有给定流量、匹配比例与解码开销时,也不能宣称固定快几个数量级。tcpdump 手册源码
常用捕获条件
tcpdump - nn - r capture.pcap ' host 192.0.2.10 and port 443'
tcpdump - nn - r capture.pcap ' ip and tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn'
tcpdump - nn - r capture.pcap ' (host 192.0.2.10 or host 192.0.2.20) and tcp port 443'
tcp[tcpflags] == tcp-syn 是标志字节严格相等,可能排除同时带其他协商标志的 SYN;按位掩码才准确表达“有 SYN、无 ACK”。原始传输头偏移表达式在 IPv6 扩展头等场景有额外限制,因此这里显式限定 IPv4,不能当成全协议通用建连统计。
端口条件不等于应用识别:443 可以承载不同协议,DNS 加密流量也不会都在 53。非首片 IP 分片没有原始传输层端口,过窄端口过滤可能让重组缺片;VLAN 和隧道封装同样影响偏移和匹配范围。pcap-filter 语法源码
换显示过滤器,能找回没抓到的包吗?
先用严格等值采集,再显示所有,只能看到一个包。改用掩码重新采集才会保存两种SYN。修改采集下拉框本身也不会改写已经保存的文件,必须明确执行新采集。
正在呈现知识画面 重新加载
换显示过滤器,能找回没抓到的包吗? 显示过滤只能在已保存集合中筛选。严格SYN等值会漏掉同时置ECE/CWR的SYN,事后显示全部也不能补回。
{"id":"capture-filter-evidence","title":"换显示过滤器,能找回没抓到的包吗?","summary":"显示过滤只能在已保存集合中筛选。严格SYN等值会漏掉同时置ECE/CWR的SYN,事后显示全部也不能补回。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><label class=\"form-field\"><span data-i18n=\"capture\"></span><select id=\"capture\"><option value=\"exact\">flags == 0x02</option><option value=\"mask\">(flags & 0x12) == 0x02</option><option value=\"all\">TCP443: all</option></select></label><div class=\"actions\"><button id=\"record\" data-i18n=\"record\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><label class=\"form-field\"><span data-i18n=\"display\"></span><select id=\"display\"><option value=\"all\">All saved packets</option><option value=\"syn\">SYN bit set</option></select></label><section class=\"panel\"><h3 data-i18n=\"sets\"></h3><div id=\"sets\"></div></section><div class=\"metrics\"><div><span data-i18n=\"saved\"></span><strong id=\"saved\"></strong></div><div><span data-i18n=\"shown\"></span><strong id=\"shown\"></strong></div></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n#sets .tr>*{min-height:3.6em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}const flags=[2,194,16];let saved=[];function draw(){const shown=saved.filter(i=>$('#display').value==='all'||flags[i]&2);table('sets',[['flags','saved','displayed'],...flags.map((n,i)=>['0x'+n.toString(16).padStart(2,'0'),saved.includes(i)?'✓':'∅',shown.includes(i)?'✓':'∅'])]);set('saved',saved.length);set('shown',shown.length);$('#sets').dataset.snapshot=JSON.stringify({saved,shown});}$('#record').onclick=()=>{const mode=$('#capture').value;saved=[0,1,2].filter(i=>mode==='all'||(mode==='exact'?flags[i]===2:(flags[i]&18)===2));draw()};$('#display').onchange=draw;$('#reset').onclick=()=>{saved=[];draw()};draw();","audio":false,"strings":{"capture":"采集条件","display":"显示条件","heading":"换显示过滤器,能找回没抓到的包吗?","intro":"先选择采集条件并保存一次,再改变显示条件。两层集合分开保留。","record":"按当前条件采集","reset":"重置","saved":"保存包数","scope":"三个固定Ethernet/IPv4/TCP包,flags02/c2/10,都涉及443。模型按位计算过滤,不执行抓包;完整pcap与tcpdump离线验证保留在正文。","scopeLabel":"模型范围与约定","sets":"候选、已保存、已显示","shown":"显示包数"}}
可控地采集,再离线分析
以下为实际排查时按环境调整的命令。网卡名和目标地址需要替换,文件位置应保证可写;采集接口通常需要相应权限,读取保存文件一般不需要。
tcpdump - i eth0 - nn - s 0 - B 4096 - c 5000 \
- w capture.pcap ' host 192.0.2.10 and port 443'
tcpdump - i eth0 - nn - s 0 - C 100 - W 5 \
- w trace.pcap ' host 192.0.2.10'
tcpdump - nn - tttt - r capture.pcap
-s 0 使用工具所定义的最大/default snaplen,不是数学上的无限长度;检查文件内的 captured length 与 original length,才能知道某个包是否被截断。-B 的单位是 KiB。-C 轮换可能稍超过阈值,不能当成精确磁盘上限;与 -W 的组合会循环覆盖旧文件。按时间的 -G 与计数选项有不同交互,不应直接照搬容量轮换逻辑。
结束时记录 captured、received by filter 和 dropped by kernel。后两者的口径依赖操作系统;dropped by kernel 表示采集机制的缓冲丢弃,不等于业务网络实际丢包数。即使显示 0,也不保证镜像口、驱动或其他位置没有漏采。
一个粗略存储预算:平均每秒 20,000 个包、平均保留 800 B,经典 pcap 每记录另有 16 B 头,60 秒约 20,000 × 816 × 60 = 979,200,000 B,即 979.2 MB。该估计未计文件系统开销,也不适用于直接套算 pcapng 的所有块结构。
先定位流,再看重组与方向
tshark - r capture.pcap - q - z conv,tcp
tshark - r capture.pcap - Y ' tcp.stream == 3' - T fields \
- e frame.number - e frame.time_relative - e ip.src - e tcp.srcport \
- e tcp.seq - e tcp.ack - e tcp.len - e tcp.window_size
tshark - r capture.pcap \
- Y ' tcp.analysis.retransmission || tcp.analysis.zero_window'
IPv6 分析应同时选择 ipv6.src 等字段。没有握手时,窗口缩放等协商信息可能未知;字段空白也可能因为本包不含该层,而不是数值为 0。TShark 手册
ip.addr == A 可匹配任一方向的地址。若想排除所有涉及 A 的包,用 !(ip.addr == A) 明确表达,避免在多值字段上凭直觉推导不等操作的语义。显示过滤不会重新补回采集时丢掉的字节。显示过滤器
HTTP 头可能跨 TCP segment,压缩或加密后也未必是可直接搜索的文本。tcpdump -A | grep Host: 只能找到某些单包里的可见片段,不是可靠的 HTTP 请求解析器。使用协议重组和 Follow Stream,并明确捕获是否从连接开始。
“重传”提示是一项基于可见包的分析
序号缺口:先区分路径丢包与抓包缺失
发送端
采集点
接收端
seq=1001,len=1000
seq=2001,len=1000(此点没采到)
ACK=3001
接收端确认到了 3001,不能据中间抓包缺口直接判定漏传
这是教学假设;真实判断还需检查完整双向流、重复采集、序号回绕和分析设置。
提示或现象 先解释成什么 继续核对
Previous segment not captured 当前可见序号前有缺口 抓包是否中途开始、漏采或过滤过窄
Retransmission / Out-of-order 可见序号与已有流状态出现重复或乱序关系 双向 ACK、时序、重复采集和分析设置
ZeroWindow 该报文发送者通告接收窗口为零 这一端应用读取与接收缓冲状态
Bad checksum 当前采样字节计算结果不符或验证条件不足 本机发送卸载、截断及抓包位置
大于常规 MTU 的“包” 主机观察到一个较大的处理单元 TSO/GSO/GRO/LRO 是否参与
Wireshark 的 TCP 分析依赖捕获包的次序和可见历史,其分类可以有歧义,不能把每项提示都当成对端 TCP 实现直接报告的事实。TCP Analysis
发送端抓包可能发生在网卡补校验和、分段之前;接收端也可能观察到聚合后的数据。此时错误校验和或“大包”不必然出现在线路上。若要确认真实线上形态,应比较接收端或外部观察点;关闭卸载会改变性能与行为,不应成为看到提示后的默认第一步。Wireshark Offloading
TLS 解密需要正确会话秘密,不是只填一个变量名
SSLKEYLOGFILE 是部分客户端和 TLS 库支持的密钥日志接口,并非操作系统能强制所有程序输出秘密的通用开关。程序必须实际支持、启动时读取配置并产生与捕获会话匹配的日志。
在 Wireshark 的 TLS 协议设置中指定日志文件,或离线使用:
tshark - r capture.pcap - o tls.keylog_file:session.keys - Y http
解密后协议不一定是 HTTP/1,HTTP/2 等需要相应过滤器。只有服务端证书私钥,不能解密采用前向安全交换的 ECDHE 会话或通常的 TLS 1.3 会话;历史 RSA 密钥交换的私钥解密有严格适用条件。握手、记录或日志缺失也会妨碍重组和解密。Wireshark TLS
密钥日志与对应抓包合在一起会暴露该会话内容,应按所含会话数据管理访问和保留范围。解密后的可见内容仍取决于应用是否还有独立的端到端加密。
离线验证过滤语义:两种 SYN 和一个 ACK
下面生成一个临时 Ethernet/IPv4/TCP pcap,用已安装的 tcpdump 读取并过滤,不连接接口、不发送报文。三个包分别是 SYN、SYN+ECE+CWR、ACK;精确等于 SYN 只能匹配一个,掩码条件应匹配两个。
import ipaddress
import struct
import subprocess
import tempfile
from pathlib import Path
def checksum ( data ) :
if len ( data ) % 2 : data += b ' \0 '
total = sum ( struct . unpack ( ' >' + ' H' * ( len ( data ) / / 2 ) , data ) )
while total > > 16 : total = ( total & 0x ffff) + ( total > > 16 )
return ( ~ total ) & 0x ffff
src = ipaddress . ip_address ( ' 192.0.2.1' ) . packed
dst = ipaddress . ip_address ( ' 192.0.2.10' ) . packed
packets = [ ]
for flags in [ 0x 02, 0x c2, 0x 10] :
tcp = struct . pack ( ' >HHIIBBHHH' , 40000 , 443 , 1000 , 0 , 0x 50, flags , 65535 , 0 , 0 )
pseudo = src + dst + struct . pack ( ' >BBH' , 0 , 6 , len ( tcp ) )
tcp = tcp [ : 16 ] + struct . pack ( ' >H' , checksum ( pseudo + tcp ) ) + tcp [ 18 : ]
ip = struct . pack ( ' >BBHHHBBH4s4s' , 0x 45, 0 , 40 , 1 , 0 , 64 , 6 , 0 , src , dst )
ip = ip [ : 10 ] + struct . pack ( ' >H' , checksum ( ip ) ) + ip [ 12 : ]
ethernet = bytes . fromhex ( ' 0200000000020200000000010800' )
packets . append ( ethernet + ip + tcp )
with tempfile . TemporaryDirectory ( ) as folder :
path = Path ( folder ) / ' flags.pcap'
output = struct . pack ( ' <IHHIIII' , 0x a1b2c3d4, 2 , 4 , 0 , 0 , 65535 , 1 )
for i , packet in enumerate ( packets ) :
output += struct . pack ( ' <IIII' , 1700000000 + i , 0 , len ( packet ) , len ( packet ) )
output += packet
path . write_bytes ( output )
filters = [
( ' ip and tcp[tcpflags] == tcp-syn' , 1 ) ,
( ' ip and tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn' , 2 ) ,
( ' tcp port 443' , 3 ) ,
]
for expression , expected in filters :
result = subprocess . run ( [ ' tcpdump' , ' -nn' , ' -r' , str ( path ) , expression ] ,
text = True , capture_output = True , check = True )
assert len ( result . stdout . strip ( ) . splitlines ( ) ) == expected
print ( ' 离线 pcap:严格 SYN 1 个,掩码 SYN 2 个,443 共 3 个' )
这验证的是捕获表达式与包编码的匹配,不是性能测试,也没有模拟完整 TCP 会话。真实排查应把原始文件、采集命令、工具版本、采集计数和关键帧号一起记录,使结论可以复查。