Linux 网络实验(1)

实验一:你好,Namespace (两台主机直连)

第一个实验,我们要创建两个虚拟主机(ns1ns2),并通过一对"虚拟网线"(Veth Pair)将它们连接起来,模拟两台电脑直连的场景。

1. 拓扑结构

text 复制代码
+-----------------+             +-----------------+
| Namespace: ns1  |             | Namespace: ns2  |
|                 |             |                 |
| IP: 192.168.1.1 | <---------> | IP: 192.168.1.2 |
|      (veth1)    |             |     (veth2)     |
+-----------------+             +-----------------+

2. 实战命令

第一步:创建虚拟环境

bash 复制代码
# 创建两个命名空间
sudo ip netns add ns1
sudo ip netns add ns2

# 创建一对虚拟网线 (veth pair)
sudo ip link add veth1 type veth peer name veth2

# 把网线两头分别插到两个房间里
sudo ip link set veth1 netns ns1
sudo ip link set veth2 netns ns2

第二步:配置 IP 并启动

注意:新创建的网卡默认是 Down 状态,需要手动 Up。

bash 复制代码
# 配置 ns1
sudo ip netns exec ns1 ip addr add 192.168.1.1/24 dev veth1
sudo ip netns exec ns1 ip link set veth1 up
sudo ip netns exec ns1 ip link set lo up

# 配置 ns2
sudo ip netns exec ns2 ip addr add 192.168.1.2/24 dev veth2
sudo ip netns exec ns2 ip link set veth2 up
sudo ip netns exec ns2 ip link set lo up

第三步:连通性测试

bash 复制代码
# 从 ns1 ping ns2
sudo ip netns exec ns1 ping 192.168.1.2

如果看到回复,说明物理(虚拟)链路通了!

3. Iptables 防火墙实验

既然有了独立的协议栈,我们就可以测试防火墙规则。假设我们要禁止 ns1 访问 ns2

我们在 ns2 上配置 iptables:

bash 复制代码
# 在 ns2 的 INPUT 链(入口)丢弃来自 192.168.1.1 的 ICMP 包
sudo ip netns exec ns2 iptables -A INPUT -s 192.168.1.1 -p icmp -j DROP

再次尝试 Ping,你会发现请求被阻塞了。这证明了每一个 Namespace 确实拥有独立的 iptables 规则表。


实验二:路由器 (Routing & NAT)

在真实世界中,我们通常通过路由器访问不同网段的主机。现在我们来模拟一个经典的三节点拓扑:内网客户端 -> 路由器 -> 外网服务器

1. 拓扑结构

text 复制代码
   Namespace: client             Namespace: router              Namespace: server
+---------------------+      +-----------------------+      +---------------------+
|      (veth-c)       |      | (veth-rc)   (veth-rs) |      |       (veth-s)      |
| IP: 10.1.1.2        | <--> | 10.1.1.1     10.2.2.1 | <--> | IP: 10.2.2.2        |
| GW: 10.1.1.1        |      |                       |      | GW: 10.2.2.1        |
+---------------------+      +-----------------------+      +---------------------+
      Subnet A                       Router                     Subnet B
    (10.1.1.0/24)                                             (10.2.2.0/24)

2. 核心配置步骤

第一步:环境搭建

bash 复制代码
# 清理旧环境
sudo ip netns del ns1 2>/dev/null; sudo ip netns del ns2 2>/dev/null

# 创建三个 Namespace
sudo ip netns add client
sudo ip netns add router
sudo ip netns add server

# 创建并连接网线
sudo ip link add veth-c type veth peer name veth-rc
sudo ip link set veth-c netns client
sudo ip link set veth-rc netns router

sudo ip link add veth-rs type veth peer name veth-s
sudo ip link set veth-rs netns router
sudo ip link set veth-s netns server

第二步:配置 IP 和 默认网关 (Routing)

这是最关键的一步,Client 和 Server 必须知道"不知道往哪发的时候,就发给 Router"。

bash 复制代码
# --- Client ---
sudo ip netns exec client ip link set lo up
sudo ip netns exec client ip link set veth-c up
sudo ip netns exec client ip addr add 10.1.1.2/24 dev veth-c
# 设置网关指向 Router 左手
sudo ip netns exec client ip route add default via 10.1.1.1

# --- Router (两张网卡) ---
sudo ip netns exec router ip link set lo up
sudo ip netns exec router ip link set veth-rc up
sudo ip netns exec router ip addr add 10.1.1.1/24 dev veth-rc
sudo ip netns exec router ip link set veth-rs up
sudo ip netns exec router ip addr add 10.2.2.1/24 dev veth-rs

# --- Server ---
sudo ip netns exec server ip link set lo up
sudo ip netns exec server ip link set veth-s up
sudo ip netns exec server ip addr add 10.2.2.2/24 dev veth-s
# 设置网关指向 Router 右手
sudo ip netns exec server ip route add default via 10.2.2.1

第三步:开启 Router 的转发功能

Linux 默认出于安全考虑是禁止转发数据包的。我们要手动开启它,让它变成真正的路由器。

bash 复制代码
sudo ip netns exec router sysctl -w net.ipv4.ip_forward=1

此时,client 已经可以 ping 通 server 了。

3. NAT 魔法:SNAT 与 DNAT

为了模拟真实的家庭上网体验,我们要在 Router 上配置 NAT。

场景 A:内网访问外网 (SNAT / Masquerade)

我们要让 Server 觉得所有流量都是 Router 发的,隐藏 Client 的真实 IP。

bash 复制代码
# 在 POSTROUTING 链做源地址转换
sudo ip netns exec router iptables -t nat -A POSTROUTING -o veth-rs -j MASQUERADE

验证: 在 server 上抓包 (tcpdump -i veth-s),client 此时 ping server,server 看到的源 IP 将是 10.2.2.1 (Router),而不是 10.1.1.2

场景 B:外网访问内网 (DNAT / Port Forwarding)

假设 Client 在 8000 端口运行了一个 Web 服务(python3 -m http.server 8000),我们希望 Server 通过访问 Router 的 80 端口来访问它。

bash 复制代码
# 在 PREROUTING 链做目标地址转换
sudo ip netns exec router iptables -t nat -A PREROUTING -d 10.2.2.1 -p tcp --dport 80 -j DNAT --to-destination 10.1.1.2:8000

验证: Server 执行 curl 10.2.2.1:80,实际上访问到了 Client 的文件服务器。


总结

通过这两个实验,在 Linux 内核中复现了:

  1. L3 路由转发 (Routing Table)
  2. 防火墙规则 (Filter Table)
  3. 地址转换 (NAT Table)

这是为你准备的"深度思考"板块。你可以把它作为博客的**"进阶分析""排错与思考"**章节放在实验步骤之后。这部分内容往往是区分"只会敲命令的人"和"真正懂原理的人"的关键。


深度思考与排错分析

1. ARP 的"薛定谔"状态:为什么 Ping 总是在发 ARP,而 TCP 却很安静?

现象描述:

当我们清除 ARP 缓存并开始 ping 另一台主机时,如果你在 Switch 上抓包,会发现一个有趣的现象:

  • 使用 Ping (ICMP) 时: 即使网络一直通畅,每隔几秒钟(默认约 5-6 秒),发送端还是会发出一个 ARP Request ("Who has 192.168.1.x?")。
  • 使用 Netcat (TCP) 时: 只要连接建立并持续传输数据,除了最开始的一次 ARP,之后网络非常安静,完全没有后续的 ARP 广播

深度解析:Linux 的 NUD 状态机

这涉及到 Linux 内核邻居子系统(Neighbor Subsystem)的 NUD (Neighbor Unreachability Detection) 机制。

Linux 的 ARP 缓存是有"保质期"的。当一个 ARP 条目超过 base_reachable_time(默认 30秒)后,状态会变成 STALE (陈旧)

  • 对于 Ping (ICMP): 内核比较保守。虽然收到了 Ping 回包,但它认为这不足以完全证明链路层的双向可达性。当内核再次需要通过这个 STALE 的地址发包时,会进入 DELAY -> PROBE 状态,强行发送 ARP 请求来再次确认 MAC 地址。
  • 对于 TCP: TCP 是面向连接的协议,具有 ACK (确认应答) 机制。当 TCP 连接正常传输时,接收到的 ACK 包会被内核视为**"上层协议确认" (Upper Layer Protocol Confirmation)**。TCP 协议栈会告诉 ARP 子系统:"嘿,我刚收到确认,这个人还活着!"。于是 ARP 状态直接从 STALE 刷新回 REACHABLE,完全跳过了发送 ARP 请求的步骤。

结论: Linux 内核非常智能,它利用 TCP 的 ACK 机制"免费"维护了 ARP 表的活性,从而减少了网络中的广播风暴。


2. NAT 的"自动挡"机制:为什么回包不需要配置 SNAT?

现象描述:

在实验二(DNAT 端口映射)中,我们配置了如下规则:

  • DNAT (进站): 访问 Router:80 -> 转给 Client:8000
  • 我们并没有配置 SNAT (出站): 没有规则告诉 Router,当 Client 回复 Server 时,要把源 IP 改回 Router。

疑问: 既然没配 SNAT,Client 回复给 Server 的包,源 IP 应该是 Client 的内网 IP。Server 收到这个包(源 IP 不匹配),应该会丢弃才对。为什么实验却成功了?

深度解析:Conntrack (连接跟踪)

这就是 Linux "有状态防火墙 (Stateful Firewall)" 的核心魔力。iptablesnat 表有一个黄金法则:

NAT 表的规则,只对连接的"第一个数据包"生效。

  1. 第一阶段 (Request): Server 发出的第一个 SYN 包到达 Router。Router 匹配到 DNAT 规则,修改目标 IP,并且 在内核的"连接跟踪表" (conntrack table) 中记录下这次修改:

    • "记录:Server(1.2.3.4) 找 Router(5.6.7.8),但我改成了 Client(10.0.0.1)。如果 Client 回话,记得改回去。"
  2. 第二阶段 (Reply): Client 回复的数据包到达 Router。

    • Router 的内核首先 检查 conntrack 表。
    • 它发现这个包属于一个已建立的连接 (ESTABLISHED)
    • 自动操作: 根据之前的记录,内核自动执行反向的 SNAT 操作(把源 IP 改回 Router IP),完全不需要用户手动写 iptables 规则。
    • 这个包甚至根本不会去查 nat 表。

验证方法:

你可以在 Router 上执行 conntrack -L,会看到类似这样的记录:

text 复制代码
tcp ... src=Server dst=Router ... [UNREPLIED] src=Client dst=Server ...

这就是内核默默为你工作的证据。

结论: 在 Linux 中做 NAT,我们只需要关注"去程","回程"是全自动的

没问题!这确实是一个非常关键的知识点,很多初学者(甚至有经验的工程师)容易在这里绕晕。

把这段关于 "SNAT 和 DNAT 是否会打架(冲突)" 的讨论加入到博客的"深度思考"部分,能完美解释为什么我们在同一个路由器上既能配上网(SNAT),又能配端口映射(DNAT)。

以下是为你补充的第三个深度思考板块:


3. SNAT vs DNAT:它们会"打架"吗?(谁先发起谁说了算)

疑问描述:

在实验中,我们在 Router 上同时配置了两条规则:

  1. SNAT (Masquerade): 为了让内网 Client 能上网。
  2. DNAT (Port Forwarding): 为了让外网 Server 能访问内网服务。

这引发了一个经典的逻辑陷阱:"当数据包经过路由器时,这两条规则会不会冲突?比如 Server 访问 Client 时,会不会错误的触发了 SNAT 规则?"

深度解析:方向决定一切

答案是绝对不会冲突。Linux 内核通过判断**"是谁先发起的连接"**(Who Pings Who),来决定使用哪套逻辑。

这里的核心原则依然是基于 Conntrack(连接跟踪) 的状态机制:

  • 场景 A:Client 主动找 Server (Client Ping Server)

    • 动作: Client 发出第一个包 (SYN)。
    • 流程: 数据包从内网流向外网。
    • 命中规则: 内核检查 POSTROUTING 链,匹配到 SNAT 规则。
    • 结果: 源 IP 被伪装。内核记录:"这是一个 SNAT 连接"。
    • 回包: Server 回复时,内核看到是 SNAT 连接的回包,自动还原目标 IP。DNAT 规则根本不会被触发。
  • 场景 B:Server 主动找 Client (Server Ping Router)

    • 动作: Server 发出第一个包 (SYN) 访问 Router IP。
    • 流程: 数据包从外网流向内网。
    • 命中规则: 内核检查 PREROUTING 链,匹配到 DNAT 规则。
    • 结果: 目标 IP 被修改为 Client IP。内核记录:"这是一个 DNAT 连接"。
    • 回包: Client 回复时,内核看到是 DNAT 连接的回包,自动还原源 IP。SNAT 规则被完全无视。

一句话总结:

SNAT 守着出口(负责出去),DNAT 守着入口(负责进来)。它们就像大楼的"出站闸机"和"进站闸机",互不干扰。谁先发起了握手,谁就定义了这条连接的性质。


相关推荐
chlk1231 天前
Linux文件权限完全图解:读懂 ls -l 和 chmod 755 背后的秘密
linux·操作系统
舒一笑1 天前
Ubuntu系统安装CodeX出现问题
linux·后端
改一下配置文件1 天前
Ubuntu24.04安装NVIDIA驱动完整指南(含Secure Boot解决方案)
linux
BingoGo2 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack2 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
深紫色的三北六号2 天前
Linux 服务器磁盘扩容与目录迁移:rsync + bind mount 实现服务无感迁移(无需修改配置)
linux·扩容·服务迁移
SudosuBash2 天前
[CS:APP 3e] 关于对 第 12 章 读/写者的一点思考和题解 (作业 12.19,12.20,12.21)
linux·并发·操作系统(os)
哈基咪怎么可能是AI2 天前
为什么我就想要「线性历史 + Signed Commits」GitHub 却把我当猴耍 🤬🎙️
linux·github
BingoGo3 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack3 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端