二十、实验、抓包与学习方法
内容摘要:本章提供一个可运行、可复现、可清理的统一实验项目:用 Linux 网络命名空间(Network Namespace,netns)搭出 client / router / server 三节点拓扑,配置 IPv4+IPv6 与转发,跑一个 TCP 服务、一个 UDP 服务、一次 DNS 查询、一次 HTTP/TLS 请求,做一份受控 PCAP,注入一次延迟/丢包故障,最后完整清理;并给出计算机网络的学习顺序与网络图绘制方法。
难度:中级 | 前置知识:01–03 基础、总目录第八节 实验流程 | 所属层次:贯穿二到七层
⚠️ 执行状态声明(诚实标注):本知识库当前验证主机为 macOS 26.6.2,不具备
ip/ss/tc/nftables/Linuxnetns。因此下文所有 netns / tc / ss 步骤均标记为「未在本环境执行」,命令按 Linux LTS + iproute2 语法编写,可直接在 Linux(物理机、虚拟机或容器,需 root 或CAP_NET_ADMIN)复现。文末「已验证部分」列出确实在本机跑通的dig/curl/openssl客户端命令与真实输出。绝不伪造 netns 输出。
一、实验目标
- 用 netns 构建隔离拓扑,不触碰宿主机真实网络与生产环境。
- 打通二层(veth)、三层(IPv4/IPv6 路由与转发),验证端到端连通。
- 在 server 上跑 TCP、UDP、DNS、HTTP/TLS 四类服务,从 client 访问。
- 抓取一份受控 PCAP,解释关键报文。
- 用
tc netem注入延迟与丢包,对比故障前后指标。 - 一键清理,恢复宿主机到实验前状态。
二、网络拓扑图
- 地址遵循 RFC 5737(IPv4 文档地址
192.0.2.0/24、198.51.100.0/24)与 RFC 3849(IPv6 文档地址2001:db8::/32),不可路由到真实业务。 - router 命名空间开启 IPv4/IPv6 转发,充当 client 网段与 server 网段之间的三层设备。
三、操作系统、内核、工具与镜像版本
| 项 | 基线 | 核验方式 |
|---|---|---|
| 操作系统 | Linux LTS 发行版(如 Ubuntu 22.04/24.04 LTS) | cat /etc/os-release |
| 内核 | 支持 netns/veth/netem 的 LTS 内核 | uname -r |
| 工具 | iproute2(ip/ss/tc)、iputils(ping)、tcpdump、python3、dig(dnsutils/bind-utils)、curl、openssl | ip -V、tc -V、tcpdump --version |
| 权限 | root 或 CAP_NET_ADMIN(netns/tc)、CAP_NET_RAW(tcpdump/ping) | id |
[未在本环境执行]:以上版本核验命令在 Linux 运行。本机(macOS)无
ip/ss/tc,故这些命令未执行。
四、地址规划、接口、路由与端口
| 角色 | netns | 接口 | IPv4 | IPv6 | 默认路由(IPv4 / IPv6) |
|---|---|---|---|---|---|
| client | cli | veth-c | 192.0.2.2/24 | 2001:db8:2::2/64 | 192.0.2.1 / 2001:db8:2::1 |
| router | rtr | veth-rc | 192.0.2.1/24 | 2001:db8:2::1/64 | —(转发) |
| router | rtr | veth-rs | 198.51.100.1/24 | 2001:db8:100::1/64 | —(转发) |
| server | srv | veth-s | 198.51.100.2/24 | 2001:db8:100::2/64 | 198.51.100.1 / 2001:db8:100::1 |
| 服务 | 端口 | 协议 |
|---|---|---|
| TCP echo/HTTP | 8080 | TCP |
| UDP echo | 9999 | UDP |
| DNS(可选,见第九节) | 5353 | UDP/TCP |
| HTTPS(自签名,见第十节) | 8443 | TCP/TLS |
五、环境创建命令(含依赖检查)
[未在本环境执行 — Linux 可直接复现]。建议保存为脚本
lab-up.sh,sudo bash lab-up.sh。
1、依赖检查
#!/usr/bin/env bash
set -euo pipefail
# 依赖检查:缺失即退出,避免中途失败
for bin in ip ss tc tcpdump python3; do
command -v "$bin" >/dev/null 2>&1 || { echo "缺少依赖: $bin"; exit 1; }
done
[ "$(id -u)" -eq 0 ] || { echo "需要 root 或 CAP_NET_ADMIN"; exit 1; }
echo "依赖检查通过"2、创建命名空间与 veth,配置地址
# 1) 创建三个网络命名空间
ip netns add cli
ip netns add rtr
ip netns add srv
# 2) 创建两对 veth,并把两端分别放入对应命名空间
ip link add veth-c type veth peer name veth-rc
ip link add veth-s type veth peer name veth-rs
ip link set veth-c netns cli
ip link set veth-rc netns rtr
ip link set veth-rs netns rtr
ip link set veth-s netns srv
# 3) client 侧地址与默认路由
ip -n cli addr add 192.0.2.2/24 dev veth-c
ip -n cli addr add 2001:db8:2::2/64 dev veth-c
ip -n cli link set veth-c up
ip -n cli link set lo up
ip -n cli route add default via 192.0.2.1
ip -n cli -6 route add default via 2001:db8:2::1
# 4) router 两侧地址
ip -n rtr addr add 192.0.2.1/24 dev veth-rc
ip -n rtr addr add 2001:db8:2::1/64 dev veth-rc
ip -n rtr addr add 198.51.100.1/24 dev veth-rs
ip -n rtr addr add 2001:db8:100::1/64 dev veth-rs
ip -n rtr link set veth-rc up
ip -n rtr link set veth-rs up
ip -n rtr link set lo up
# 5) server 侧地址与默认路由
ip -n srv addr add 198.51.100.2/24 dev veth-s
ip -n srv addr add 2001:db8:100::2/64 dev veth-s
ip -n srv link set veth-s up
ip -n srv link set lo up
ip -n srv route add default via 198.51.100.1
ip -n srv -6 route add default via 2001:db8:100::1
# 6) 在 router 命名空间开启转发(仅影响该 netns,不动宿主机)
ip netns exec rtr sysctl -w net.ipv4.ip_forward=1
ip netns exec rtr sysctl -w net.ipv6.conf.all.forwarding=1安全说明:转发只在
rtr命名空间内开启(ip netns exec rtr sysctl),不修改宿主机全局net.ipv4.ip_forward,避免副作用。所有地址均为 RFC 5737/3849 文档地址,不会泄漏到真实网络。
六、连通性验证(正常路径)
[未在本环境执行 — Linux 可直接复现]
# 接口与地址(预期各接口 UP、地址正确)
ip -n cli -br address
ip -n rtr -br address
ip -n srv -br address
# IPv4 端到端(client → server),预期 0% 丢包
ip netns exec cli ping -c 3 198.51.100.2
# IPv6 端到端,预期 0% 丢包
ip netns exec cli ping -c 3 2001:db8:100::2
# 路由查找:确认走 router
ip netns exec cli ip route get 198.51.100.2 # 预期 via 192.0.2.1 dev veth-c
ip netns exec cli ip -6 route get 2001:db8:100::2 # 预期 via 2001:db8:2::1预期输出(示意,非本机实测):ping 显示 3 packets transmitted, 3 received, 0% packet loss;ip route get 显示下一跳为 192.0.2.1。
七、TCP 服务与客户端
在 srv 起一个最简 TCP 服务(Python 标准库,无第三方依赖),在 cli 连接。
1、服务端(srv 命名空间)
ip netns exec srv python3 - <<'PY' &
import socketserver
class Echo(socketserver.StreamRequestHandler):
def handle(self):
data = self.rfile.readline().strip()
# 注意 TCP 是字节流:一次 recv 未必对应一次 send,这里用换行做应用层 framing
self.wfile.write(b"echo:" + data + b"\n")
with socketserver.TCPServer(("198.51.100.2", 8080), Echo) as s:
s.serve_forever()
PY
sleep 1
# 确认监听(预期看到 LISTEN 在 198.51.100.2:8080)
ip netns exec srv ss -lntp 'sport = :8080'2、客户端(cli 命名空间)
# 用 nc 或 python 连接并发送一行
printf 'hello-tcp\n' | ip netns exec cli python3 - <<'PY'
import socket, sys
s = socket.create_connection(("198.51.100.2", 8080), timeout=3)
s.sendall(sys.stdin.buffer.read())
print("recv:", s.recv(1024).decode().strip())
s.close()
PY预期:客户端打印 recv: echo:hello-tcp;ss 在服务端显示 ESTAB(连接期间)。关键教学点:TCP 是字节流,服务端用 readline()(换行)做应用层 framing,这正是「TCP 不是消息协议」的直接体现(详见 13)。
八、UDP 服务与客户端
1、服务端(srv)
ip netns exec srv python3 - <<'PY' &
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("198.51.100.2", 9999))
while True:
data, addr = s.recvfrom(2048)
s.sendto(b"echo:" + data, addr) # UDP 无连接:每个数据报独立,不保证到达/顺序
PY
sleep 1
ip netns exec srv ss -lnup 'sport = :9999'2、客户端(cli)
ip netns exec cli python3 - <<'PY'
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(3) # UDP 要自己处理超时(无连接语义)
s.sendto(b"hello-udp", ("198.51.100.2", 9999))
print("recv:", s.recvfrom(2048)[0].decode())
PY预期:recv: echo:hello-udp。教学点:UDP 无连接、无重传,客户端必须自己设超时;丢包时上层无感知(对比 12、14)。
九、一次 DNS 查询
实验内 DNS 有两种做法,按需选择:
方案 A(离线、纯隔离):在 srv 用 dnsmasq 起一个权威/转发 DNS(端口 5353),从 cli 用 dig 查询。
# srv: 起一个只解析 www.example → 198.51.100.2 的最小 DNS(需安装 dnsmasq)
ip netns exec srv dnsmasq --no-daemon --port=5353 \
--address=/www.example/198.51.100.2 --no-resolv &
sleep 1
# cli: 向实验内 DNS 查询(预期 ANSWER 为 198.51.100.2)
ip netns exec cli dig @198.51.100.2 -p 5353 www.example A +noall +answer预期:www.example. 0 IN A 198.51.100.2。这演示了 DNS 把「名字」解析成「实验内地址」的完整闭环(详见 15)。
方案 B(本机已验证的真实公网查询):见文末「已验证部分」。
十、一次 HTTP/TLS 请求
在 srv 用自签名证书起一个 HTTPS 服务(端口 8443),从 cli 用 curl/openssl 访问。
# srv: 生成仅用于实验的自签名证书(SAN=www.example),绝不用于任何真实域名
ip netns exec srv bash -c '
cd /tmp
openssl req -x509 -newkey rsa:2048 -nodes -keyout k.pem -out c.pem -days 1 \
-subj "/CN=www.example" -addext "subjectAltName=DNS:www.example" 2>/dev/null
python3 -c "
import http.server, ssl
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain(\"/tmp/c.pem\", \"/tmp/k.pem\")
httpd = http.server.HTTPServer((\"198.51.100.2\", 8443), http.server.SimpleHTTPRequestHandler)
httpd.socket = ctx.wrap_socket(httpd.socket, server_side=True)
httpd.serve_forever()
" &
'
sleep 1
# cli: 用 IP 直连并指定 SNI/hostname,--cacert 指向实验证书(不要用 -k 关闭校验作为习惯)
ip netns exec cli curl -sS --resolve www.example:8443:198.51.100.2 \
--cacert /tmp/c.pem https://www.example:8443/ -o /dev/null -w 'tls=%{time_appconnect}s code=%{response_code} http=%{http_version}\n'
# 或用 openssl 观察握手与证书链
ip netns exec cli openssl s_client -connect 198.51.100.2:8443 -servername www.example </dev/null 2>/dev/null | grep -E 'Protocol|Cipher|subject='安全说明:证书仅用于隔离实验、有效期 1 天、CN/SAN 为文档域名
www.example,私钥不出实验环境,实验后随命名空间一并销毁。不得将自签名证书或-k(跳过校验)习惯带入生产(见 17)。
十一、受控 PCAP 与关键报文解释
[未在本环境执行 — Linux 可直接复现]。抓包前须确认授权,并遵守本节的范围限制与脱敏要求。
# 在 router 命名空间抓 client↔server 的 TCP 8080 流量,限定 200 包,写文件
ip netns exec rtr timeout 20 tcpdump -i veth-rc -nn -s 0 -c 200 \
-w /tmp/lab-tcp.pcap 'tcp port 8080'
# 触发一次 TCP 请求(见第七节)后停止,读取关键帧
tcpdump -nn -r /tmp/lab-tcp.pcap -c 10预期关键报文序列(TCP 建连三次握手,详见 13):
| 帧 | 方向 | 关键字段 | 含义 |
|---|---|---|---|
| 1 | cli→srv | Flags [S] seq=x | SYN,client 发起 |
| 2 | srv→cli | Flags [S.] seq=y ack=x+1 | SYN-ACK |
| 3 | cli→srv | Flags [.] ack=y+1 | ACK,握手完成 |
| 4 | cli→srv | Flags [P.] len>0 | 携带 hello-tcp 数据 |
| 5 | srv→cli | Flags [P.] len>0 | 携带 echo:hello-tcp |
| … | 双向 | Flags [F.]/[.] | 四次挥手关闭 |
PCAP 脱敏:本实验 PCAP 仅含 RFC 5737 文档地址与实验载荷(
hello-tcp),无真实敏感数据;即便如此,写入知识库前仍应确认无宿主机信息泄漏(本库规范)。
十二、故障注入:延迟与丢包
[未在本环境执行 — Linux 可直接复现]。
tc netem为 Linux 专属。
# 在 client 出接口注入 100ms 延迟 + 2% 丢包
ip netns exec cli tc qdisc add dev veth-c root netem delay 100ms loss 2%
# 对比:故障前后 ping RTT 与丢包
ip netns exec cli ping -c 20 198.51.100.2 # 预期 RTT 抬升约 100ms 量级,约 2% 丢包
# TCP 吞吐/重传观察(如装了 iperf3;或看 ss 的重传计数)
ip netns exec cli ss -tin dst 198.51.100.2 # 关注 rtt/retrans 字段
# 清理故障(务必执行)
ip netns exec cli tc qdisc del dev veth-c root预期现象:注入后 ping 的 time= 从亚毫秒抬升到 ~100ms,出现约 2% 丢包;TCP 会触发重传、拥塞窗口下降(观测 ss -ti 的 retrans/cwnd,详见 14)。这直接印证「丢包会降吞吐」而非只看丢包率本身。
十三、完整清理与恢复命令
[未在本环境执行 — Linux 可直接复现]。保存为
lab-down.sh。
#!/usr/bin/env bash
# 1) 清理故障注入(若残留)
ip netns exec cli tc qdisc del dev veth-c root 2>/dev/null || true
# 2) 结束实验内后台服务
ip netns pids srv 2>/dev/null | xargs -r kill 2>/dev/null || true
ip netns pids cli 2>/dev/null | xargs -r kill 2>/dev/null || true
# 3) 删除命名空间(veth 会随之自动删除)
for ns in cli rtr srv; do ip netns del "$ns" 2>/dev/null || true; done
# 4) 删除实验产生的临时文件
rm -f /tmp/lab-tcp.pcap /tmp/c.pem /tmp/k.pem
echo "清理完成,宿主机恢复实验前状态"
# 5) 校验:应无残留命名空间
ip netns list防失联提醒:本实验全程在独立命名空间内操作,未修改宿主机路由/防火墙/默认网关,因此不存在把自己「锁在门外」的风险。真实设备上做类似变更时,必须先准备带外访问与回滚。
十四、实验验证覆盖清单
| 验证项 | 命令 | 本章覆盖 |
|---|---|---|
| 命令退出状态与关键输出 | 各命令 echo $? / 输出 | ✅ |
| 接口地址与链路状态 | ip -br address、ip -br link | ✅(第六节) |
| 路由查找与实际路径 | ip route get | ✅(第六节) |
| 监听端口与套接字状态 | ss -lntp/ss -lnup/ss -ti | ✅(第七/八/十二节) |
| 抓包协议层次/五元组/标志位 | tcpdump PCAP | ✅(第十一节) |
| 应用层响应 | TCP/UDP/DNS/HTTPS 客户端输出 | ✅ |
| 故障注入前后指标对比 | ping/ss -ti netem 前后 | ✅(第十二节) |
| 实验后资源清理 | lab-down.sh、ip netns list | ✅(第十三节) |
十五、已验证部分(本机 macOS 真实执行)
以下命令确实在本机(macOS 26.6.2)执行,访问 IANA 保留的文档域名 example.com,输出真实(随时间/地域变化):
DNS 查询(对应第九节方案 B):
dig +noall +answer example.com A实测(2026-09-07):
example.com. 284 IN A 172.66.147.243
example.com. 284 IN A 104.20.23.154TLS 握手与证书链(对应第十节):
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | grep -E 'Protocol|Cipher|subject=|Verify return code'实测(2026-09-07):
subject=/CN=example.com
Protocol : TLSv1.3
Cipher : AEAD-CHACHA20-POLY1305-SHA256
Verify return code: 0 (ok)HTTP/TLS 分阶段时延(对应第十节):
curl -sS -o /dev/null -w 'dns=%{time_namelookup}s tcp=%{time_connect}s tls=%{time_appconnect}s ttfb=%{time_starttransfer}s http=%{http_version}\n' https://example.com/实测(2026-09-07):
dns=0.002301s tcp=0.346848s tls=0.474025s ttfb=0.596131s http=2这些客户端命令验证了 DNS→TCP→TLS→HTTP 的真实链路,可与实验内自建服务对照。netns/tc/ss 部分未在本机执行,已如实标注。
十六、学习方法与网络图绘制
1、用“封装、路径、状态、证据”学习
每个主题都按四个问题学习:
- 封装:报文由哪些头部和载荷组成,字段由谁生成?
- 路径:报文经过哪些主机、接口和中间设备,每跳改变什么?
- 状态:端点或设备维护什么状态,何时创建、迁移和释放?
- 证据:用什么命令、日志、指标和抓包验证,而不是只背结论?
推荐顺序:
每学完一个协议,至少完成一次“正常流量 + 单一故障 + 清理恢复”的闭环。只看图不抓包,容易把示意图当成真实实现;只抓包不理解状态机,则容易把相关性误判成根因。
2、网络图如何选型
| 要回答的问题 | 推荐图 | 必须标注 |
|---|---|---|
| 组件如何连接 | 拓扑图/flowchart | 节点、接口、网段、方向、边界 |
| 一次请求先后发生什么 | sequenceDiagram | 参与者、消息、重传/超时、分支 |
| TCP/DHCP 等如何迁移 | stateDiagram | 状态、触发事件、超时与终止条件 |
| 报文由哪些字段组成 | 字段表/位图 | 位宽、语义、生成方、接收方处理 |
| 故障如何定位 | 决策树 | 观测条件、证据、下一步、停止条件 |
一张图只回答一个核心问题。不要把物理拓扑、逻辑服务、协议时序和监控数据全部挤在同一张图中。
3、端到端路径图模板
绘图时应根据问题补充:
- IP、MAC、端口和协议;
- DNS、NAT、代理等发生变化的位置;
- TLS 终止点与明文边界;
- 抓包点 A/B 和日志来源;
- 正常路径、失败路径与回程路径。
4、学习验收
完成本知识库后,应能在不看答案的情况下:
- 画出“输入 URL 到收到页面”的完整路径;
- 用抓包解释 TCP 握手、重传、关闭及 HTTP/TLS 层次;
- 区分 DNS、路由、端口、TLS 和应用错误;
- 解释为什么 ping 成功不代表业务成功;
- 设计一个可回滚的延迟、丢包或 MTU 故障实验;
- 对结论标注“标准规定、实现行为或本次实验观测”。
十七、常见问题与排查
| 现象 | 可能原因 | 排查 |
|---|---|---|
ping 不通 | 转发未开、地址/掩码错、默认路由缺失 | 逐节点 ip -br address、ip route、sysctl 转发 |
| IPv6 不通但 IPv4 通 | 未配 IPv6 默认路由或转发 | ip -6 route、net.ipv6.conf.all.forwarding |
ss 看不到 LISTEN | 服务绑定了错误地址或未启动 | 确认 bind 地址与命名空间 |
| netem 清理后 RTT 仍高 | qdisc 未删干净 | tc qdisc show dev veth-c 确认无 netem |
| 命名空间删不掉 | 仍有进程占用 | ip netns pids <ns> 后 kill |
十八、关联章节
- 设计与安全边界:知识库总目录 · 第八节
- 二层/三层原理:05、06、07
- TCP 逐包分析:13、14
- 抓包、故障注入与学习闭环:本章第十一、十二、十六节
检索关键词
netns 实验拓扑 veth pair ip netns exec IPv4 IPv6 转发 sysctl TCP UDP echo 服务 dig 实验 DNS 自签名 HTTPS 实验 tc netem 延迟 丢包 tcpdump PCAP 网络怎么学 网络图 时序图 状态图 实验清理 ip netns del
权威来源
- RFC 5737(IPv4 文档地址)、RFC 3849(IPv6 文档地址)、RFC 2606(文档域名)
- Linux
ip-netns(8)、ip(8)、tc-netem(8)、ss(8)、tcpdump(1)man page(以目标发行版版本为准)
本章 netns/tc/ss 命令按 Linux iproute2 编写,未在本环境(macOS 26.6.2)执行,已逐处标注;
dig/openssl/curl客户端输出为本机真实执行结果,verified_at: 2026-09-07。
