第12章 网络调试
叙事密度:中高 | 主语言:Python | Bash/Shell 差异窗 | Vol 4·计算机网络
元数据卡
| 属性 | 内容 |
|---|---|
| 卷号 | Vol 4 — 计算机网络 |
| 章节 | 第12章:网络调试 |
| 前置 | 第1章(分层模型)、第3章(TCP 深度剖析)、第4章(HTTP)、第5章(HTTPS) |
| 后置 | 第13章(IPv6 基础)、第14章(网络实战与性能优化) |
| 理论深度 | (2/5) |
| Python 相关度 | ≈60%;scapy 构造包、curl 耗时分析脚本 |
| 核心模型 | 分层排障模型、调试工具链 |
| 代码量 | ~120 行 |
你的进度
"学了这么多传送咒和驿道概念——但如果它们不工作呢?传送阵打不开、法术应用很慢、连接被拒绝——驿道调试是每个通信法师的必修课。这一章不讲新传送咒,只教你在驿道世界的施法工具里找出问题在哪里。"
前面十章的课程里,你学完了驿道分层模型的每一层——从物理层的魔力脉冲到应用层的 HTTP 和 QUIC。你知道三次法力握手、拥塞控制、TLS 加密封印、法术域名解析是怎么工作的。
但有个问题一直悬着:当这些东西不工作的时候,你该怎么办?
- "传送阵打不开"——是你写错了法术坐标,还是域名解析法阵失败,还是信标塔挂了?
- "应用很慢"——是驿道带宽不够,还是连接建立太慢,还是 TLS 封印握手卡住了?
- "连接被拒绝"——法术服务没启动,还是护城法阵挡住了?
驿道调试不是玄学。它是一门系统性的分层排障方法。前面学的每一层知识,都会在这一章变成实际武器。
本章分层
- 必读:分层排障思路、ping/mtr、dig、curl、ss、tcpdump 基本用法
- 选读:Wireshark 的 Follow TCP Stream、HTTP 请求分析
- 进阶:scapy 构造自定义包、NAT 导致的问题深入分析
本章是实战章:没有复杂的理论推导,但每个工具都需要你亲手测试才能内化。建议跟着每段命令在你的机器上跑一次。
你的任务
学完本章,你应当:
- 用"分层排障法"系统地定位网络故障——从物理层到应用层逐层排查
- 熟练使用 ping、dig、curl、tcpdump 在实战中排障
- 用 tcpdump + Wireshark 抓包分析一个真实的 HTTP 请求
- 区分"连接被拒绝"和"超时"的根本原因
- 用 openssl s_client 调试 TLS 握手失败
- 写一个 Python 脚本自动化网络性能分析
破局 · 溯源
第1战:分层排障法——"网页打不开"不是问题,只是现象
问题: 你打开法师观测镜,输入 https://example.com/path/to/page,然后观测镜一直转圈,最后报错 "无法连接驿道"。
你的第一反应可能是"我是不是写错法术坐标了"——但如果是域名解析法阵失败、驿道不通、信标塔挂了,法师观测镜都会显示相似的错误信息。
系统性的排障方法:从底层到应用层逐层排查
| 层 | 你问的问题 | 用什么工具 |
|---|---|---|
| 1⃣ 物理/链路层 | 网线插了吗?WiFi 连上了吗? | ip link、ifconfig、nmcli |
| 2⃣ 网络层 | 能通到对方吗?路由对吗? | ping、traceroute、mtr |
| 3⃣ 传输层 | 端口开着吗?连接状态正常吗? | ss、nmap、nc |
| 4⃣ DNS 层 | 域名能解析吗?IP 正确吗? | dig、nslookup、host |
| 5⃣ 应用层 | HTTP 返回了什么?TLS 握手正常吗? | curl -v、openssl s_client、tcpdump |
黄金法则: 从底往上查。如果底层不通,上层怎么查也是白费。
# 快速排障流水线(逐层检查)
# 第1层:物理层
ip link show | grep "state UP" || echo " 网络接口没起来"
# 第2层:网络层
ping -c 3 8.8.8.8 || echo " 连不上互联网"
# 第3层:传输层
ss -tlnp | grep :443 || echo " 本地443端口没监听"
# 第4层:DNS
dig +short example.com || echo " DNS 解析失败"
# 第5层:应用层
curl -v --connect-timeout 3 https://example.com/ || echo " HTTP 请求失败"第2战:网络层排障——ping、traceroute、mtr
ping——最简单的连通性测试
# 基本用法:ping IP 或域名
ping -c 5 8.8.8.8预期输出:
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=118 time=12.3 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=118 time=11.8 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=118 time=12.1 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=118 time=12.5 ms
64 bytes from 8.8.8.8: icmp_seq=5 ttl=118 time=12.0 ms
--- 8.8.8.8 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4005ms
rtt min/avg/max/mdev = 11.80/12.14/12.50/0.24 msping 告诉你的:
- 丢包率:0% = 路径正常。>0% = 路径可能拥堵或有问题
- RTT:平均 12ms 是正常。如果 RTT 突然到 200ms+ → 路由有问题或链路拥堵
- TTL:118 不太小,往回算大概跳数。如果 TTL=1,说明包都快死了
ping 不告诉你的事情:
- 服务器端口开着没(ping 用 ICMP,不是 TCP/UDP)
- 防火墙可能允许 ICMP 但阻挡 TCP(某些网络只让 ping 过)
# 用 ping 检测网络波动(持续 ping)
ping 8.8.8.8
# 用 ping 检测丢包模式:是持续丢包还是间歇性?
# 持续丢包 → 物理问题(网线、WiFi信号)
# 间歇性丢包 → 网络拥堵常见错误解读:
# "ping 通" ≠ "服务可用"
ping google.com ← ICMP echo 通了
curl https://google.com/ ← 可能 443 端口被防火墙挡了# "ping 不通" ≠ "网络断了"
ping some-server.internal ← 可能是该服务器禁用了 ICMPtraceroute——看你的包走了哪条路
traceroute -n 8.8.8.8
# -n: 不解析主机名,只显示 IP(更快)预期输出:
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
1 192.168.1.1 1.2 ms 1.0 ms 1.1 ms ← 你家路由器
2 10.0.0.1 5.3 ms 5.1 ms 5.0 ms ← ISP 网关
3 172.16.1.2 8.7 ms 12.3 ms 8.5 ms ← ISP 骨干路由器
4 72.14.238.0 11.2 ms 11.0 ms 11.5 ms ← Google 入网点
5 * * * ← 某些路由器不回复 traceroute(正常)
6 8.8.8.8 12.3 ms 11.9 ms 12.1 ms ← 终于到了traceroute 的工作原理: 发送 TTL=1 的包 → 第一跳路由器看到 TTL 过期 → 返回 ICMP Time Exceeded → 记录它的 IP。然后 TTL=2 → 第二跳回复 → 记录……直到目标。
什么时候看 traceroute:
- 延迟很高——看哪一跳突然暴增。如果跳3的 RTT 是 8ms,跳4是 200ms→那第4跳是瓶颈
- 路由路径异常——包不应该走这条路线(比如从亚洲跑到美洲再回来)
- 丢包位置——* * * 出现的位置往往能告诉你丢在哪
mtr——traceroute 的持续版(最好用的组合工具)
# mtr = ping + traceroute,持续跟踪每一跳的延迟和丢包
mtr -n 8.8.8.8 My traceroute [v0.95]
home (0.0.0.0) 2024-01-15T10:30:00+0800
Keys: Help Display mode Restart statistics Order of fields quit
Packets Pings
Host Loss% Snt Last Avg Best Wrst StDev
1. 192.168.1.1 0.0% 50 1.2 1.1 0.8 2.3 0.3
2. 10.0.0.1 0.0% 50 5.3 5.1 4.8 8.2 0.5
3. 172.16.1.2 0.0% 50 8.2 8.5 7.9 12.1 0.8
4. 72.14.238.0 25.0% 50 11.2 12.5 10.5 45.2 5.3 ← 25% 丢包!
5. ??? 100% 50 0.0 0.0 0.0 0.0 0.0
6. 8.8.8.8 0.0% 50 12.1 12.3 11.5 14.2 0.4mtr 强大的原因: 它持续发送包(不像 traceroute 只发 3 个),所以能告诉你哪一跳在丢包。上面的例子中,跳4 25% 丢包——即使最终跳(目标 8.8.8.8)0% 丢包。这意味着跳4到最终跳之间可能有负载均衡,一部分路径有问题。
第3战:DNS 层排障——dig
问题: 法师观测镜输入 https://example.com → 等了半天 → "找不到信标塔"。
你的域名解析法阵可能出了问题。用 dig 来诊断:
# 基础查询
dig example.com
# 只拿 IP 地址
dig +short example.com
# → 93.184.216.34预期完整输出:
; <<>> DiG 9.18.24 <<>> example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12345
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; QUESTION SECTION:
;example.com. IN A
;; ANSWER SECTION:
example.com. 38400 IN A 93.184.216.34
;; Query time: 45 msec
;; SERVER: 192.168.1.1#53(192.168.1.1)
;; WHEN: Mon Jan 15 10:30:00 CST 2024
;; MSG SIZE rcvd: 56dig 告诉你的关键信息:
| 字段 | 含义 | 排障用途 |
|---|---|---|
status: NOERROR | 查询成功 | 如果 NXDOMAIN → 域名不存在或拼写错误 |
ANSWER: 1 | 找到了 1 个记录 | 如果 ANSWER: 0 → DNS 不知道这个域名 |
38400 | TTL (缓存时间) | 如果 TTL=0 → 可能是临时策略或调试环境 |
Query time: 45 msec | 解析耗时 | 如果 500ms+ → DNS 服务器慢 |
SERVER: 192.168.1.1#53 | 谁回的 | 你家的路由器在解析(DNS 代理) |
高级诊断:
# 查某个记录类型
dig mx gmail.com # 邮件服务器记录
dig ns google.com # 权威名称服务器
dig txt google.com # SPF、DKIM 等文本记录
dig aaaa google.com # IPv6 地址
# 指定 DNS 服务器查询(绕过本地缓存)
dig @8.8.8.8 example.com # 用 Google DNS
dig @1.1.1.1 example.com # 用 Cloudflare DNS
# 追踪 DNS 解析链路
dig +trace example.com"dig +trace" 的威力:
; <<>> DiG 9.18.24 <<>> +trace example.com
;; Received 525 bytes from 192.168.1.1#53(192.168.1.1) in 1 ms
. 8573 IN NS a.root-servers.net. ← 根服务器
. 8573 IN NS b.root-servers.net.
;; Received 262 bytes from 198.41.0.4#53(a.root-servers.net) in 4 ms
com. 172800 IN NS a.gtld-servers.net. ← .com 顶级域
com. 172800 IN NS b.gtld-servers.net.
;; Received 878 bytes from 192.5.6.30#53(a.gtld-servers.net) in 8 ms
example.com. 172800 IN NS a.iana-servers.net. ← 权威服务器
example.com. 172800 IN NS b.iana-servers.net.
;; Received 248 bytes from 192.42.93.30#53(a.gtld-servers.net) in 20 ms
example.com. 86400 IN A 93.184.216.34 ← 最终结果DNS 排障三步:
- 域名存在吗? →
dig +short example.com→ 如果没结果,查拼写或dig +trace - 本地 DNS 有问题? → 对比
dig @8.8.8.8和dig @local-dns。如果 Google DNS 能解但本地不能 → 本地 DNS 问题 - DNS 延迟高? →
dig @8.8.8.8的Query time如果持续 >100ms → 可能网络问题
第4战:传输层排障——ss、netstat、nc
问题: ping 8.8.8.8 通了,但 curl https://example.com/ 显示 "连接被拒绝"。
驿道层没问题(ping OK),但 TCP 传送咒连接失败了。这说明目标法术端口可能没开放,或者护城法阵挡住了。
# 看本地开了哪些端口
ss -tlnp
# -t: TCP
# -l: 仅监听中的端口
# -n: 数字显示(不解析服务名)
# -p: 显示进程信息预期输出:
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1234,fd=3))
LISTEN 0 128 127.0.0.1:5432 0.0.0.0:* users:(("postgres",pid=5678,fd=5))ss 的关键用法:
# 所有连接(包括非监听)
ss -tna
# 看指定端口
ss -tlnp sport = :80
ss -tlnp dport = :443
# 实时监控
watch -n 1 'ss -s' # 连接统计概览netstat——ss 的前任(较新的 Linux 推荐用 ss):
netstat -tlnp # 等效于 ss -tlnp
netstat -s # 网络统计nc(netcat)——手动建连接:
# 测试端口是否开放(简单版)
nc -zv example.com 443
# → Connection to example.com 443 port [tcp/https] succeeded!
nc -zv example.com 8080
# → nc: connect to example.com port 8080 (tcp) failed: Connection refused"Connection refused" vs "Timeout"——核心区别:
Connection refused (ECONNREFUSED):
客户端 ─── SYN →── 服务器(端口没监听)
← RST(TCP RST 包)
客户端收到 RST → "对方拒绝了"
超时 (Timeout):
客户端 ─── SYN →── 防火墙 ──→ 服务器(端口正常)
← 防火墙丢包(什么也不回)
客户端等了好几秒 → "没有回复"| 错误 | 含义 | 常见原因 |
|---|---|---|
Connection refused | 服务器明确拒绝了 | 端口没打开、服务没启动、防火墙发 RST |
Connection timed out | 包发了但没回应 | 防火墙静默丢弃、路由不通、服务器完全没响应 |
No route to host | 路由器告诉我"找不到路" | IP 不可达、路由表缺失 |
第5战:应用层排障——curl -v
问题: 传送层通了(法术端口开放),但传送阵页面还是加载不了——可能是 HTTP 层面或 TLS 层面的问题。
curl -v 是最好的应用层调试工具。
# 详细模式:看整个 HTTP 请求/响应的每一个步骤
curl -v https://example.com/预期输出:
* Trying 93.184.216.34:443...
* Connected to example.com (93.184.216.34) port 443 (#0)
* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server accepted http/1.1
* Server certificate:
* subject: CN=example.com
* start date: Jun 10 00:00:00 2024 GMT
* expire date: Sep 8 23:59:59 2024 GMT
* subjectAltName: host "example.com" matched cert's "example.com"
* issuer: C=US; O=Let's Encrypt; CN=R3
* SSL certificate verify ok.
> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Age: 549561
< Cache-Control: max-age=604800
< Content-Type: text/html; charset=UTF-8
< Date: Mon, 15 Jan 2024 02:30:00 GMT
< Etag: "3147526947"
< Server: ECS (dcb/7EC6)
< Content-Length: 1256
<
<!doctype html>
<html>
...每一行意味着什么:
* Trying 93.184.216.34:443... ← DNS 解析成功,开始 TCP 连接
* Connected to example.com ... ← TCP 三次握手完成
* TLSv1.3 ... Client hello ← TLS 握手开始
* TLSv1.3 ... Certificate ← 服务器发证书
* SSL certificate verify ok ← 证书验证通过(重点!失败会显示错误)
> GET / HTTP/1.1 ← 你的 HTTP 请求
< HTTP/1.1 200 OK ← 服务器的 HTTP 响应curl 的诊断参数:
# 只看响应头(不下载页面内容)
curl -I https://example.com/
# 只显示耗时统计
curl -w "TCP 连接: %{time_connect}s\nTLS 握手: %{time_appconnect}s\n首字节: %{time_starttransfer}s\n总耗时: %{time_total}s\n" -so /dev/null https://example.com/输出:
TCP 连接: 0.012s
TLS 握手: 0.035s
首字节: 0.048s
总耗时: 0.048s常见 curl 排障场景:
# 场景1:HTTP 重定向(检查是否被重定向了)
curl -vL https://example.com/ # -L 自动跟随重定向
# 场景2:自定义 Header 看后端怎么处理
curl -v -H "X-Forwarded-For: 10.0.0.5" https://example.com/
# 场景3:代理调试
curl -v -x http://proxy.example.com:8080 https://example.com/
# 场景4:忽略证书错误(只用于测试)
curl -k https://self-signed.badssl.com/第6战:TLS 调试——openssl s_client
问题: curl 报错 SSL certificate problem: self signed certificate 或 certificate has expired——你怀疑是 TLS 法力握手出了问题。
openssl s_client 是 TLS 调试的瑞士军刀:
# 基本用法:建立 TLS 连接并输出完整信息
openssl s_client -connect example.com:443预期输出(关键部分):
CONNECTED(00000003)
depth=2 C=US, O=Internet Security Research Group, CN=ISRG Root X1
verify return:1
depth=1 C=US, O=Let's Encrypt, CN=R3
verify return:1
depth=0 CN=example.com
verify return:1
---
Certificate chain
0 s:CN=example.com
i:C=US, O=Let's Encrypt, CN=R3
a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
1 s:C=US, O=Let's Encrypt, CN=R3
i:C=US, O=Internet Security Research Group, CN=ISRG Root X1
2 s:C=US, O=Internet Security Research Group, CN=ISRG Root X1
i:C=US, O=Internet Security Research Group, CN=ISRG Root X1
---
SSL handshake has read 4466 bytes and written 379 bytes
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 2048 bit
---常见调试场景:
# 检查证书过期
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates
# → notBefore=Jun 10 00:00:00 2024 GMT
# → notAfter=Sep 8 23:59:59 2024 GMT
# 检查证书链是否完整
openssl s_client -connect example.com:443 -showcerts
# 检查是否支持指定的 TLS 版本
openssl s_client -tls1_2 -connect example.com:443 # 只尝试 TLS 1.2
openssl s_client -tls1_3 -connect example.com:443 # 只尝试 TLS 1.3
# 测试 SNI(服务器名称指示)
openssl s_client -connect 1.2.3.4:443 -servername example.com"证书错误"排障流水线:
- 检查证书是否过期:
openssl x509 -noout -dates - 检查证书域名是否匹配:curl 时看
subjectAltName是否包含请求的域名 - 检查证书链是否完整:
openssl s_client -showcerts— 有些服务器没发中间证书 - 检查是否自签名证书:
verify error:num=18:self signed certificate - 检查系统 CA 包:
openssl version -d— 看 CA 证书目录
第7战:抓包——tcpdump + Wireshark
问题: 以上工具能告诉你"出了什么问题",但不能告诉你"到底发生了什么"——你想要的是协议层面的证据。哪一步失败?谁发的什么法术包?
tcpdump——命令行抓包神器
# 抓所有流量(慎用——可能会产生海量数据)
sudo tcpdump -i any
# 基本过滤:只看特定端口
sudo tcpdump -i any port 80
# 只看特定主机的 HTTP 流量
sudo tcpdump -i any host example.com and port 80
# 只抓几个包(限制了数量,适合快速调试)
sudo tcpdump -i any port 443 -c 10
# 保存到文件(后续用 Wireshark 分析)
sudo tcpdump -i any port 443 -w capture.pcaptcpdump 过滤表达式(必记基础):
# 协议过滤
sudo tcpdump icmp # 只看 ping
sudo tcpdump tcp # 只看 TCP
sudo tcpdump udp # 只看 UDP
# 主机过滤
sudo tcpdump host 8.8.8.8 # 和 8.8.8.8 通信的包
sudo tcpdump src host 8.8.8.8 # 从 8.8.8.8 发出的源包
sudo tcpdump dst host 192.168.1.5 # 发给 192.168.1.5 的目的包
# 端口过滤
sudo tcpdump port 443 # 443 端口
sudo tcpdump src port 53 # 源端口 53(DNS 响应)
sudo tcpdump dst port 22 # 目的端口 22(SSH 进来的)
# 组合过滤
sudo tcpdump "host 8.8.8.8 and (port 53 or port 443)"
sudo tcpdump "tcp[tcpflags] & (tcp-syn|tcp-ack) != 0" # SYN-ACK 包用 tcpdump 看三次握手:
# 监控 TCP 连接建立(三次握手)
sudo tcpdump -i any "host example.com and tcp[tcpflags] & (tcp-syn) != 0" -c 3输出:
12:00:01.123456 IP 192.168.1.5.54321 > 93.184.216.34.443: Flags [S], seq 1000 ← SYN
12:00:01.135678 IP 93.184.216.34.443 > 192.168.1.5.54321: Flags [S.], seq 2000, ack 1001 ← SYN-ACK
12:00:01.135690 IP 192.168.1.5.54321 > 93.184.216.34.443: Flags [.], ack 2001 ← ACK用 tcpdump 看 HTTP 请求/响应的交互:
sudo tcpdump -i any port 80 -A # -A: ASCII 输出,能看见 HTTP 内容12:00:02.123456 IP 192.168.1.5.54322 > 93.184.216.34.80: Flags [P.], seq 1:77
E..q..@.@..X....x.."..5..K.....Z.......
GET / HTTP/1.1
Host: example.com
...Wireshark——图形化协议分析
tcpdump 抓的 pcap 文件用 Wireshark 打开,你会发现一个完全不同级别的调试体验。
Wireshark 的核心功能:
- 捕获过滤器(Capture Filter)——抓的时候过滤,语法和 tcpdump 一样
- 显示过滤器(Display Filter)——抓完后的数据分析过滤:
# 基本过滤
http # 只看 HTTP 包
tcp.port == 443 # 只看 443 端口的包
ip.addr == 1.2.3.4 # 只看和这个 IP 通信的包
# 高级过滤
tcp.flags.syn == 1 and tcp.flags.ack == 0 # 只看 SYN 包(握手第一步)
http.request.method == "POST" # 只看 POST 请求
tls.handshake.type == 11 # TLS Certificate 消息
http.time > 0.5 # 响应时间超过 0.5 秒的 HTTP 请求- Follow TCP Stream——右键 → Follow → TCP Stream,你会看到一个完整的 HTTP 请求/响应对话,就像读一封信的往来:
← [SYN] 我来了
→ [SYN, ACK] 收到!我也来了
← [ACK] 好!连接建立
← [PSH, ACK] GET /index.html HTTP/1.1
→ [ACK] 收到请求
→ [PSH, ACK] HTTP/1.1 200 OK ...
← [ACK] 收到内容
→ [FIN, ACK] 结束抓包黄金场景:
| 场景 | 抓包方案 |
|---|---|
| 网页很慢 | 抓 HTTP/HTTPS 流量,看每个请求的响应时间分布 |
| 某个接口间歇性失败 | 抓 100 个请求,找失败的包模式 |
| 登录失败 | 抓应用层包,看是否登录请求被重定向了 |
| 证书错误 | 抓 TLS 握手包,看 Certificate 消息的具体内容 |
| DNS 解析问题 | tcpdump port 53,看 DNS 请求/响应 |
| 连接突然中断 | 抓 TCP 包,看 RST 或 FIN 是谁发的 |
第8战:端口扫描——nmap
问题: 你想知道"这座信标塔上哪些法术端口是开放的"——或者你的信标塔上是不是无意中开了不该开的端口。
# 基本扫描(TCP SYN 扫描,默认不需要完成三次握手)
nmap -sS example.com
# 扫描指定端口范围
nmap -sS -p 1-1000 example.com
# 快速扫描(常用端口)
nmap -F example.comStarting Nmap 7.80 ( https://nmap.org ) at 2024-01-15 10:30 CST
Nmap scan report for example.com (93.184.216.34)
Host is up (0.041s latency).
Not shown: 996 filtered ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
443/tcp open https
8080/tcp closed http-proxy
Nmap done: 1 IP address (1 host up) scanned in 5.23 seconds端口状态及其含义:
| 状态 | 含义 | 推测 |
|---|---|---|
open | 端口开放,有程序在监听 | 正常服务 |
closed | 收到 RST | 端口没开任何服务,也没有防火墙阻挡 |
filtered | 没收到任何响应(或收到 ICMP 不可达) | 防火墙在过滤这个端口 |
| `open | filtered` | 无法区分 open 和 filtered |
进阶:Python scapy 构造自定义包
以下内容属于自定义网络包分析,主线不要求掌握。如果你需要深入网络协议分析或编写网络测试工具,再读这里。
前面 tcpdump 只能看现成的包。如果你想要构造自己的包来测试网络行为,Python 的 scapy 库是最强大的工具。
# python_scapy_demo.py
# 安装: pip install scapy
# 运行前注意: scapy 需要 root 权限构造原始包
# 警告:不要对未经授权的服务器运行扫描!
from scapy.all import *
# 例1:手动构造 TCP SYN 包并等待回复
def syn_scan(host: str, port: int):
"""
发送一个 TCP SYN 包到指定端口
如果收到 SYN-ACK → 端口开放
如果收到 RST-ACK → 端口关闭
"""
ip = IP(dst=host)
syn = TCP(sport=RandShort(), dport=port, flags="S")
# 发送包并等待回复(超时 2 秒)
reply = sr1(ip / syn, timeout=2, verbose=False)
if reply is None:
return f"Port {port}: filtered (no response)"
elif reply.haslayer(TCP):
if reply[TCP].flags == 0x12: # SYN-ACK
# 礼貌地关闭握手(否则对方会等)
rst = IP(dst=host) / TCP(sport=reply[TCP].dport,
dport=port, flags="R")
send(rst, verbose=False)
return f"Port {port}: open"
elif reply[TCP].flags == 0x14: # RST-ACK
return f"Port {port}: closed"
return f"Port {port}: unknown"
# 例2:构造完整的 HTTP GET 请求包
def http_get_raw(host: str, port: int = 80):
"""
手动构造 TCP 三次握手 + HTTP GET 请求的完整交互
等同于: curl http://host/
"""
# 创建套接字(需要 root 权限)
conf.L3socket = L3RawSocket
# 1. SYN
ip = IP(dst=host)
syn = TCP(sport=RandShort(), dport=port, flags="S", seq=1000)
syn_ack = sr1(ip / syn, timeout=2, verbose=False)
if syn_ack is None:
print("No response to SYN")
return
# 2. ACK (完成三次握手)
my_seq = syn_ack[TCP].ack
my_ack = syn_ack[TCP].seq + 1
ack = TCP(sport=syn[TCP].sport, dport=port, flags="A",
seq=my_seq, ack=my_ack)
send(ip / ack, verbose=False)
# 3. HTTP GET 请求
http_request = (
"GET / HTTP/1.1\r\n"
f"Host: {host}\r\n"
"Connection: close\r\n"
"\r\n"
)
get_pkt = TCP(sport=syn[TCP].sport, dport=port, flags="PA",
seq=my_seq, ack=my_ack) / http_request
response = sr1(ip / get_pkt, timeout=2, verbose=False)
if response:
# 4. 发送最后的 ACK
final_ack = TCP(sport=syn[TCP].sport, dport=port, flags="A",
seq=response[TCP].ack, ack=response[TCP].seq + 1)
send(ip / final_ack, verbose=False)
print(f"Got {len(response[TCP].payload)} bytes of response:")
print(response[TCP].payload.load.decode("utf-8", errors="replace")[:500])
# 注意:实际扫描时记得:
# 1. 只扫自己的机器或授权的服务器
# 2. 设置合适的超时避免长时间等待
# 3. 构造 RST 包关闭握手(礼貌扫描)
if __name__ == "__main__":
# 测试本地端口(不需要 root 权限也可以测 localhost)
result = syn_scan("127.0.0.1", 22)
print(result)如果需要更实用的场景,scapy 也支持构造 DNS 查询包:
# 构造 DNS 查询看解析全过程
from scapy.all import *
def dns_query(domain: str, dns_server: str = "8.8.8.8"):
"""手动构造和发送 DNS 查询,查看响应"""
dns_req = IP(dst=dns_server) / UDP(dport=53) / DNS(rd=1, qd=DNSQR(qname=domain))
reply = sr1(dns_req, timeout=2, verbose=False)
if reply and reply.haslayer(DNS):
dns = reply[DNS]
for i in range(dns.ancount):
rr = dns.an[i]
print(f"{rr.rrname.decode()} → {rr.rdata}")
dns_query("google.com")
# → google.com. → 142.250.80.14实战场景排障大全
场景1:"Connection refused" vs "Timeout"
现象: curl https://example.com/
curl: (7) Failed to connect to example.com port 443: Connection refused
排障步骤:
1⃣ 能 ping 通吗? → ping example.com → 能通 → 网络层没问题
2⃣ 端口开放? → nc -zv example.com 443 → Connection refused
3⃣ 服务在跑? → ssh 到服务器 `ss -tlnp | grep 443` → 没有监听443
→ 服务挂了!→ `systemctl restart nginx`
→ 如果是"Connection timed out" vs "Connection refused":
超时 = 没任何回复 → 防火墙或路由问题
拒绝 = 有RST回复 → 端口没开或者防火墙发RST
抓包确认:sudo tcpdump -i any port 443
超时 → 你发了SYN,什么都没回来
拒绝 → 你发了SYN,回了RST场景2:DNS 解析失败
现象: curl https://example.com/
curl: (6) Could not resolve host: example.com
排障步骤:
1⃣ 基本DNS查询 → dig +short example.com → 啥也没有
2⃣ 指定DNS服务器 → dig @8.8.8.8 example.com → 能解析!
→ 问题在本地DNS配置或路由器
3⃣ 检查本地DNS → cat /etc/resolv.conf
→ nameserver 192.168.1.1 ← 你家路由器在DNS代理
4⃣ 检查路由器DNS → 登录路由器管理页面 → DNS 配置错误!
→ 改为 8.8.8.8 / 1.1.1.1场景3:SSL/TLS 握手失败
现象: curl https://self-signed.badssl.com/
curl: (60) SSL certificate problem: self signed certificate
排障步骤:
1⃣ 完整握手信息 → openssl s_client -connect self-signed.badssl.com:443
→ deep 0 CN=self-signed.badssl.com
→ verify error:num=18:self signed certificate
→ 确实是自签名证书
2⃣ 证书详情 → echo | openssl s_client -connect ... | openssl x509 -text
→ 没找到受信任 CA 的签发者
3⃣ 证书过期? → openssl x509 -noout -dates → 还在有效期内
→ 如果是生产环境:安装正确证书
→ 如果是测试环境:curl -k 或配置 CA 信任链场景4:性能瓶颈定位
现象: 应用很慢——哪个环节最耗时?
排障步骤:
1⃣ curl 耗时分析:
curl -w "@curl-format.txt" -o /dev/null -s https://example.com/创建一个 curl-format.txt 文件:
time_namelookup: %{time_namelookup}s\n
time_connect: %{time_connect}s\n
time_appconnect: %{time_appconnect}s\n
time_starttransfer: %{time_starttransfer}s\n
----------\n
time_total: %{time_total}s\n用 Python 自动化分析:
#!/usr/bin/env python3
"""curl_time_analyzer.py — 分析 curl 请求的耗时分布"""
import subprocess
import json
import sys
from statistics import mean, stdev
def curl_with_timing(url: str, count: int = 5) -> dict:
"""
多次请求同一个 URL,收集耗时指标
返回: 各阶段的平均耗时
"""
format_vars = [
"time_namelookup", "time_connect", "time_appconnect",
"time_starttransfer", "time_total"
]
# 一次性 curl 输出 JSON
format_str = "{" + ",".join(
f'"{v}":"%{{{v}}}"' for v in format_vars
) + "}"
times = {v: [] for v in format_vars}
for i in range(count):
result = subprocess.run(
["curl", "-w", format_str, "-o", "/dev/null", "-s",
"--connect-timeout", "5", url],
capture_output=True, text=True, timeout=10
)
data = json.loads(result.stdout)
for v in format_vars:
times[v].append(float(data[v]))
print(f"\n=== 耗时分析: {url} ({count} 次请求) ===\n")
print(f"{'阶段':<25} {'平均':>8} {'最小':>8} {'最大':>8} {'标准差':>8}")
print("-" * 60)
for v in format_vars:
avg = mean(times[v])
mn = min(times[v])
mx = max(times[v])
sd = stdev(times[v]) if len(times[v]) > 1 else 0
label = {
"time_namelookup": "DNS 解析",
"time_connect": "TCP 连接",
"time_appconnect": "TLS 握手",
"time_starttransfer": "首字节到达",
"time_total": "总耗时",
}[v]
print(f"{label:<25} {avg:>8.3f}s {mn:>8.3f}s {mx:>8.3f}s {sd:>8.3f}s")
# 诊断
avg_dns = mean(times["time_namelookup"])
avg_tcp = mean(times["time_connect"]) - avg_dns
avg_tls = mean(times["time_appconnect"]) - mean(times["time_connect"])
avg_server = mean(times["time_starttransfer"]) - mean(times["time_appconnect"])
avg_transfer = mean(times["time_total"]) - mean(times["time_starttransfer"])
print(f"\n{'-'*40}")
print(f"瓶颈诊断:")
if avg_dns > 0.2:
print(f" DNS 解析慢 ({avg_dns:.3f}s): 可能 DNS 服务器需要优化")
if avg_tcp > 0.5:
print(f" TCP 连接慢 ({avg_tcp:.3f}s): 可能网络延迟大")
if avg_tls > 0.5:
print(f" TLS 握手慢 ({avg_tls:.3f}s): 可能证书链长或服务器压力大")
if avg_server > 1.0:
print(f" 服务器处理慢 ({avg_server:.3f}s): 应用层可能是瓶颈")
if avg_transfer > 5.0:
print(f" 数据传输慢 ({avg_transfer:.3f}s): 带宽可能是瓶颈")
return times
if __name__ == "__main__":
url = sys.argv[1] if len(sys.argv) > 1 else "https://www.google.com/"
curl_with_timing(url)python3 curl_time_analyzer.py https://www.google.com/预期输出:
=== 耗时分析: https://www.google.com/ (5 次请求) ===
阶段 平均 最小 最大 标准差
DNS 解析 0.003s 0.002s 0.004s 0.001s
TCP 连接 0.012s 0.011s 0.014s 0.001s
TLS 握手 0.028s 0.026s 0.031s 0.002s
首字节到达 0.038s 0.036s 0.042s 0.002s
总耗时 0.148s 0.142s 0.156s 0.005s
----------------------------------------
瓶颈诊断:
各阶段都正常根据耗时分布定位瓶颈:
| 耗时分布模式 | 可能的根因 |
|---|---|
| DNS 解析 >200ms | DNS 服务器配置问题或缓存未命中 |
| TCP 连接 >500ms | 网络延迟大——远距离通信或拥塞 |
| TLS 握手 >1s | 服务器 CPU 压力大、证书链太长 |
| 首字节到达 >2s | 服务器端处理慢——应用代码、数据库查询等 |
| 数据传输 >5s | 带宽不足或大文件 |
常见陷阱
陷阱1:tcpdump 只抓了一半的包
场景: 你怀疑某个 HTTP 请求没有到达服务器。你用 tcpdump 抓包,分析之后发现——只看到了 SYN 没看到 SYN-ACK——"服务器没回应!"
可能原因: tcpdump 没有抓到双向流量。
# 错误:
sudo tcpdump -i eth0 port 80 # 只抓 eth0 接口
# 正确:
sudo tcpdump -i any port 80 # 抓所有接口
# 更稳妥的方法(双向验证):
# 在客户端抓:确保你已经打开了"混杂模式"(promiscuous mode)
# 在服务器端也抓一份:对比两边看到的包诊断方法: 如果 -i any 还是只看到单向流量,就在目标服务器上也抓一份。两边对比。
陷阱2:防火墙规则误判
场景: 你用 nmap -sS 扫描服务器,发现 80 端口是 filtered 状态——"被防火墙挡住了!"
但管理员说"我没设防火墙啊"。
真相: 你的 nmap 扫描包可能被ISP 或云提供商的边界防火墙过滤了。这不是目标服务器的防火墙。
# 用不同的方法验证:
# 1. 如果你有服务器的 SSH 权限
ssh user@server 'nc -zv localhost 80' # 本地看 80 是否监听
# 2. 如果你没有服务器权限
curl -v http://server:80/ # 如果 server 发回了数据,说明端口正常
# curl 失败不代表端口关闭——可能是:
# - HTTP 响应被拦截
# - TLS 证书不匹配
# - 应用层重定向
# 3. 检查 cloud vendor 安全组规则
aws ec2 describe-security-groups ...
gcloud compute firewall-rules list ...陷阱3:NAT 导致的问题
场景: 你部署了一个 Web 服务器在家庭网络里,外网通过公网 IP 访问不了。你用 curl localhost:8080 能正常访问,但外网用户说无法连接。
问题: 你的驿道信标塔在做传送门地址转换(NAT)。信标塔有一个公网驿道坐标(如 219.123.45.67),内部驿站网络是 192.168.1.x。外部法术请求映射到你的服务器时:
外部用户 你家的路由器 你的服务器
│ │ │
│── 连接 219.123.45.67:8080 ──────────→│ │
│ │── DNAT: 219.123.45.67:8080 ──→│
│ │ → 192.168.1.5:8080 │
│ │ │
│ │← 回复源为 192.168.1.5:8080 ───│
│ │ 源IP 是私有IP! │
│ │ SNAT 没配好 → 外网用户收不到 │诊断方法:
# 在服务器上抓包看源 IP
sudo tcpdump -i any port 8080
# 外部请求进来时,你看到源 IP 是:
# - 外部用户的真实 IP → NAT 配置正确
# - 192.168.1.1(路由器内网地址)→ SNAT 把用户IP丢了!
# 另一个症状:服务器上看到所有连接都来自路由器内网
ss -tn | grep 8080
# 应该看到外部 IP 而不是 192.168.1.1通关挑战
挑战1:跟踪一次完整的 HTTP/HTTPS 请求()
简单但最好的练习
# Step 1: 用 dig 查目标 IP
dig +short example.com
# Step 2: 用 traceroute 看路由
traceroute -n example.com
# Step 3: 用 tcpdump 抓包
sudo tcpdump -i any host example.com -w http-trace.pcap -c 50
# Step 4: 在另一个终端执行 HTTP 请求
curl http://example.com/
# Step 5: 用 tcpdump 读包
tcpdump -r http-trace.pcap -v
# Step 6: 用 Wireshark 打开 pcap 文件分析
# → Follow TCP Stream → 你看到了完整的 HTTP 请求/响应完成后回答:这次请求经过了哪些步骤?哪一步最耗时?
挑战2:"连接被拒绝" vs "超时"——自己造两种场景()
# 用 Python 启动一个具体测试服务器
python3 -m http.server 9999 &
# 场景A:连接被拒绝 —— 关闭服务器后请求
kill %1
curl -v http://localhost:9999/ # → Connection refused
# 场景B:超时 —— 防火墙拦截(本地 iptables 模拟)
sudo iptables -A INPUT -p tcp --dport 9999 -j DROP
curl -v --connect-timeout 5 http://localhost:9999/ # → Timeout
# 用 tcpdump 看看两个场景的包有什么区别
sudo tcpdump -i lo port 9999
# 清理
sudo iptables -D INPUT -p tcp --dport 9999 -j DROP挑战3:用 curl 分析你的博客/网站耗时()
# 创建 curl-format.txt 文件(如上面的内容)
cat > /tmp/curl-format.txt << 'EOF'
time_namelookup: %{time_namelookup}s\n
time_connect: %{time_connect}s\n
time_appconnect: %{time_appconnect}s\n
time_starttransfer: %{time_starttransfer}s\n
----------\n
time_total: %{time_total}s\n
EOF
curl -w "@curl-format.txt" -o /dev/null -s "https://你的网站.com/"瓶颈诊断: 如果 time_connect(TCP 连接)占了大头——检查网络延迟。如果 time_starttransfer(从 TLS 到首字节)占了大头——服务器代码慢或后端延迟高。
挑战4:Python 脚本——端口扫描器()
用 Python 的 socket 模块(不是 scapy)写一个简单的 TCP 端口扫描器:
import socket
import sys
def scan_port(host: str, port: int, timeout: float = 1.0) -> str:
"""用 socket 连接测试端口是否开放"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((host, port))
sock.close()
if result == 0:
return "open"
elif result == 111: # ECONNREFUSED
return "closed"
elif result == 110: # ETIMEDOUT
return "filtered"
else:
return f"unknown({result})"
def scan_ports(host: str, ports: list):
"""扫描多个端口"""
for port in ports:
status = scan_port(host, port)
print(f"Port {port}: {status}")
# 测试:扫描常见的 HTTP/HTTPS 端口
scan_ports("example.com", [22, 80, 443, 8080, 3306])验收标准
完成本章后,你应当可以:
- [ ] 用分层排障法系统性定位"网页打不开"的根因
- [ ] 用 ping + traceroute + mtr 排查网络层问题(丢包、延迟异常)
- [ ] 用 dig 排查 DNS 解析失败(DNS 不通、域名不存在、本地 vs 公共 DNS)
- [ ] 用 curl -v 诊断 HTTP/TLS 问题(连接被拒绝、证书错误、重定向)
- [ ] 用 ss 检查端口和服务状态
- [ ] 区分"连接被拒绝"(ECONNREFUSED)和"超时"的根本差异
- [ ] 用 tcpdump 抓包并导出为 pcap 文件供 Wireshark 分析
- [ ] 用 openssl s_client 调试 TLS 证书问题
- [ ] 知道什么时候该怀疑 NAT、什么时候该怀疑防火墙
常见卡点
| 卡点 | 原因 | 解药 |
|---|---|---|
| tcpdump 抓包需要 root 权限 | tcpdump 创建原始套接字需要 CAP_NET_RAW | 用 sudo 或给二进制加 cap:sudo setcap cap_net_raw+ep /usr/sbin/tcpdump |
| 自己的服务从外网访问不了,但 localhost 可以 | 很可能在监听 127.0.0.1 而不是 0.0.0.0 | ss -tlnp 看 Local Address 列——如果是 127.0.0.1:8080,改为 0.0.0.0:8080 或 systemctl edit 修改配置 |
| ping 能通但 curl 连不上 | ICMP 协议和 TCP 协议是两套 | ping 用 ICMP,curl 用 TCP——防火墙可能允许 ICMP 但阻止 TCP 端口 |
| dig 查到的 IP 和浏览器访问的不同 | CDN/负载均衡器根据 DNS 解析位置返回不同 IP | dig @8.8.8.8 和 dig @1.1.1.1 的结果可能不同——GeoDNS |
| tcpdump 只抓到单向流量 | 只抓了入站接口,没抓出站接口 | 用 -i any 抓所有接口,或确保接口的双向流量都被捕获 |
| openssl s_client 连不上但 curl 可以 | curl 可能在用 HTTP/2 或 HTTP/3,而 openssl 只用 HTTP/1 | openssl s_client -alpn h2 或直接用 curl --http1.1 排除协议影响 |
现在不需要理解
- eBPF 网络调试:Linux 内核的动态调试工具,可以绕过 tcpdump 直接在内核过滤。强大但需要内核编程知识——属于高级系统调试,不是日常网络排障。
- iperf3 的详细参数:
iperf3是带宽测试工具,本章只提了名字。参数很多(并行流、UDP 模式、反向测试),深入需要单独一页。 - Wireshark 的 1000+ 显示过滤器:记住基础的
http、tcp.port、ip.addr、tls.handshake就够了,其他按需学习。Wireshark 有完整的参考文档。 - SDN 网络排障:软件定义网络(OpenFlow、OVS)的调试需要不同的工具集(ovs-appctl、ovs-ofctl),属于数据中心运维场景。
- strace + lsof:这两个虽然不是网络专用工具,但在调试"进程为什么不接受连接"场合非常有用——
strace -p <pid> -e network跟踪系统调用,lsof -i :8080看哪个进程在用端口。
旅人笔记
驿道调试可能是驿道法术里最实用的技能——它把之前 11 章学到的所有抽象概念(分层、TCP、域名解析法阵、TLS、HTTP)变成了实际法具。
核心心法:"传送阵打不开/应用很慢"是现象,不是问题。排障是逐层挖掘真相的过程:
- 物理/魔力链路层 →
ip link/ ping 通不通? - 驿道层 →
ping/traceroute/mtr——能到吗?哪一跳慢/丢包? - 传送咒层 →
ss/nc/nmap——法术端口开了吗? - 域名解析法阵 →
dig——法术域名解析了吗? - 应用层 →
curl -v/openssl s_client——HTTP/TLS 正常吗?
每一层的问题都必须在这一层解决,不能跨层猜测。物理层不通 → 修魔导缆线,不是改 curl 配置。
排障的黄金时间:问题发生时立刻抓魔力包。等 5 分钟再去,传送咒可能已经关闭/咒语缓存清空,关键证据已经没了。
一句话消化本章: 遇到驿道故障,从底往上逐层排查——物理层→驿道层→传送咒层→域名解析法阵→应用层,每个法具只回答一个简单的是/否问题,组合起来就是真相。
→ 下一站预告
IPv6——当我们用完了 IPv4 地址
你排障时可能遇到过一些让人困惑的场景:IPv6 优先还是 IPv4 优先?双栈环境下 DNS 返回了两个 IP 地址哪个更优先?某些网络 IPv6 可以通但 IPv4 不行(或者反过来)。
下一章,我们不只讲 IPv6 地址的格式,而是回答一个更实际的问题:为什么你需要的不是更多的地址,而是一个全新的网络层协议?