【计算机网络】| 从 MAC 到 NAT 的跨网段通信全链路

👋 关于作者

你好,这里是一条 泥憨鱼 | 专注于 后端开发 、AI 前沿技术与计算机基础 。

🏠 个人主页 :一条泥憨鱼

通过技术博客记录技术成长,分享学习心得与经验。期待与您共同交流!

📚精选技术专栏:

|-------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|
| ❄️后端开发 | ❄️Redis| 黑马点评 |
| ❄️从0开始学习计算机网络 | ❄️数据结构与算法 |
| ❄️苍穹外卖 | ❄️探秘AI与agent |

一条 ping 超时的命令背后,藏着 ARP 广播、帧头重写、TTL 递减、路由查表和 NAT 地址替换。这篇按数据包逐跳被处理的顺序,讲清 MAC、ARP、IP、路由和 NAT 各自改写了什么、破坏了哪条假设,最后给出一条能照着走的排查序列。

同一网段和跨网段,差别只在"下一跳交给谁"

主机怎么决定一个目的 IP 是"直接发"还是"交给网关"。

判断依据只有一条------子网掩码。主机拿自己的 IP 和掩码算出网段,再拿目的 IP 和同一个掩码算出网段,两者相同就是同网段,不同就是跨网段。以 192.168.1.10/24 为例,掩码是 255.255.255.0,本机网段是 192.168.1.0/24。目的地址 192.168.1.20 落在这个范围内,属于同网段;10.20.30.40 不在,属于跨网段。

这个判断决定了 ARP 问谁。

ARP 的作用是把 IP 地址解析成 MAC 地址,但它的请求是以广播形式发出的,广播只能在同一个广播域内传播,路由器默认不转发广播。所以 ARP 的能力边界非常明确:它只能解析同一广播域内的地址。

同网段时,主机直接 ARP 目的 IP,问"谁是 192.168.1.20",对方回应自己的 MAC。跨网段时,主机不去 ARP 目的 IP(问了也没人应答,广播出不了网关),而是 ARP 自己的默认网关,问"谁是 192.168.1.1"。拿到网关 MAC 后,把包发给网关,由网关继续往下转。

路由表就是这套判断的落地形式。一条典型的路由表长这样:

bash 复制代码
$ ip route

default via 192.168.1.1 dev eth0 proto dhcp metric 100

192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.10

第一条是默认路由,default 等价于 0.0.0.0/0,匹配任何目的地址,兜底用。第二条是本网段直连路由,scope link 表示目的地址就在这条链路上,不需要经过网关。

ip route get 能直接告诉你某个目的地址最终会走哪条路、下一跳是谁:

bash 复制代码
$ ip route get 10.20.30.40

10.20.30.40 via 192.168.1.1 dev eth0 src 192.168.1.10



$ ip route get 192.168.1.20

192.168.1.20 dev eth0 src 192.168.1.10

注意第二条没有 via,说明是同网段直发。这条命令在排查时非常有用,它把"主机认为下一跳是谁"这件事直接摊开给你看,省得靠掩码手算。

以太网帧在每一跳都被重写,IP 包才端到端不变

理解了下一跳,再看封装。一个 IP 包要发出去,必须被塞进以太网帧里,帧头包含源 MAC 和目的 MAC。这里的关键是分清哪些字段逐跳变化、哪些端到端不变。

MAC 地址是链路层的局部标识,只在当前这一段链路上有意义。每经过一个路由器,帧被完整拆掉,路由器查完路由表,用新的源 MAC(自己的出接口)和新的目的 MAC(下一跳的 MAC)重新封装一个帧。所以 MAC 源和目的逐跳都在变。

IP 地址是网络层的端到端标识,路由器转发时只看目的 IP 决定往哪走,不改写它(NAT 场景除外,下一节讲)。所以 IP 源和目的在纯路由转发中**保持不变**。

TTL 是 IP 头里唯一每跳必减的字段。每经过一个路由器减一,减到 0 就丢弃,并回一个 ICMP Time Exceeded 给源地址。这个机制防止包在路由环路里无限打转。traceroute 就是利用它工作的:故意发 TTL 为 1、2、3...... 的包,让路径上每个路由器依次暴露自己。

用一次跨三跳的抓包视角看,帧头重写的具体字段是这样:

bash 复制代码
第 1 跳  主机 → R1

  以太网: src=主机MAC      dst=网关MAC

  IP:     src=192.168.1.10 dst=10.20.30.40  ttl=64



第 2 跳  R1 → R2

  以太网: src=R1出接口MAC   dst=R2入接口MAC

  IP:     src=192.168.1.10 dst=10.20.30.40  ttl=63



第 3 跳  R2 → 目标主机

  以太网: src=R2出接口MAC   dst=目标主机MAC

  IP:     src=192.168.1.10 dst=10.20.30.40  ttl=62

这张对照表值得记住,因为它解释了很多现象。比如为什么在中间路由器上抓包,看到的源 IP 仍然是原始主机而不是上一跳路由器------因为 IP 头没变。又比如为什么 TTL 突然从 64 跳到 62,说明中间过了两台路由器,可能有一条你没意识到的路径。

TTL 耗尽的表现是 ping 收到 Time to live exceeded,而不是超时。这两个报错含义完全不同:前者说明包到达了某个路由器但活不到终点,后者说明包压根没回来。排查时先区分这两者,能省掉一半弯路。

路由是逐跳的局部决策,不是端到端的全局规划

很多人潜意识里以为网络有一个"全局地图",源主机知道整个路径。实际上不是。每个路由器只做一件事:拿到包,看目的 IP,查自己的路由表,选一个出口,交给下一跳。它不知道也不需要知道下一跳之后会怎么走。

查表规则是最长前缀匹配。路由表里可能同时存在 10.0.0.0/8、10.20.0.0/16、10.20.30.0/24 三条,目的地址 10.20.30.40 会匹配前缀最长的那条(/24),因为前缀越长,覆盖范围越精确,说明这条路由的意图越具体。

默认路由 0.0.0.0/0 的前缀长度是 0,是最短匹配,所以它只在没有任何更具体的路由命中时才生效。这就是它能兜底的原因------不是优先级高,而是优先级最低,其他都匹配不上才轮到它。

bash 复制代码
$ ip route

10.20.30.0/24 via 192.168.1.1 dev eth0

10.20.0.0/16 via 192.168.1.2 dev eth0

default via 192.168.1.1 dev eth0

上面这张表里,去 10.20.30.40 会走第一条,去 10.20.99.1 会走第二条,去 8.8.8.8 走默认。

这里有个容易被忽略的事实:回程路径和去程路径通常不对称。去程可能经过 R1→R2→R3,回程因为对端路由表的配置,可能走 R3→R4→R1。互联网上这是常态,不是故障。

不对称本身没问题,但它会让两类排查变难。一是状态防火墙:防火墙按五元组记录连接状态,如果回包从另一个接口进来,可能匹配不上已有状态而被丢弃,表现为"去程通、回程不通"。二是抓包:在一台路由器上抓到的单向流量,不代表路径断了,可能只是回程走了别的路。所以抓包排查时,最好在源和目的两端同时抓,再对比。

NAT 改写了什么,又破坏了哪条假设

前面说 IP 地址端到端不变,这个结论在 NAT 面前失效。

NAT 的典型场景是内网主机访问公网。内网地址 192.168.1.10 在公网上不可路由,所以出口设备(路由器或专门的 NAT 网关)会把源 IP 换成自己的公网地址,同时换掉源端口,并把这个映射记下来。

映射表的核心是一个五元组到五元组的转换:

bash 复制代码
内网侧                        公网侧

192.168.1.10:54321  →  203.0.113.5:40001

协议 TCP                      协议 TCP

目的 10.20.30.40:80           目的 10.20.30.40:80

回包到达时,NAT 设备拿目的地址和目的端口(也就是公网侧的 203.0.113.5:40001)反查这张表,找到对应的内网地址端口,把目的改回去,再转发给内网主机。所以 NAT 是有状态的,它必须记住每一条连接。

Linux 上这张表就是 conntrack,可以用命令观察:

bash 复制代码
$ conntrack -L

tcp 6 431999 ESTABLISHED src=192.168.1.10 dst=10.20.30.40 sport=54321 dport=80 \

    src=10.20.30.40 dst=203.0.113.5 sport=80 dport=40001

注意这一行里同时出现了两个方向的地址:前半段是原始方向(内网到外网),后半段是回程方向(外网到内网)。NAT 设备就是靠这个对应关系把回包改回去的。

NAT 带来的几个后果值得单独说。

**端口耗尽。**一个公网 IP 的端口范围是 65535 个,减去系统保留的,实际可用更少。当内网并发连接数超过这个量,新的 SNAT 就分配不到端口,表现为连接建立失败。高并发服务(爬虫、压测、大量短连接)很容易撞上这个限制。

**连接跟踪表项老化。**conntrack 表项有超时时间,TCP 已建立连接通常几天,UDP 只有几十秒到几分钟。如果一条长连接长时间没有数据,表项被回收,之后的包找不到映射,就会被丢弃。这就是为什么有些长连接需要靠心跳保活------不只是防止中间设备断连,也是在维持 NAT 表项。

**外部主动入访不行。**NAT 表是内网主动出访时创建的,外部主机不知道 203.0.113.5:40001 对应哪台内网机器,也没有表项可查,包到了直接被丢。要让外部能访问,必须预先配置端口映射(DNAT),把某个公网端口静态指向内网地址。这是"内网能出去、外面进不来"的根本原因,跟防火墙策略无关,是 NAT 机制本身决定的。

一个典型的 NAT 后回包匹配失败案例:内网主机发起连接,NAT 设备创建了表项,但回包到达时源端口和预期不符(比如对端做了负载均衡,回包从另一台机器发出),五元组匹配不上,包被丢弃。现象是 TCP 握手一直停在 SYN_SENT。这时候看 conntrack 表会发现表项存在但状态是 SYN_SENT,说明回包没到或者没匹配上,下一步就该去 NAT 设备上抓包确认。

把链路串起来:一次失败的 ping 该按什么顺序查

现在把前面几节串成一条排查路径。顺序很重要,从近到远,每一步都排除掉一段链路。

第一步,本机路由。

确认主机认为下一跳是谁。

bash 复制代码
$ ip route get 10.20.30.40

10.20.30.40 via 192.168.1.1 dev eth0 src 192.168.1.10

预期看到 via 后面是网关地址。如果显示 Network is unreachable,说明路由表里没有匹配项,连默认路由都没有,问题就在本机。

第二步,网关可达性。

确认 ARP 能解析到网关,且网关能 ping 通。

bash 复制代码
$ ip neigh show 192.168.1.1

192.168.1.1 dev eth0 lladdr aa:bb:cc:dd:ee:ff REACHABLE

$ ping -c 3 192.168.1.1

预期 ARP 状态是 REACHABLE 或 STALE。如果是 FAILED 或表里没有这一项,说明二层就不通,问题在本地链路或交换机,跟跨网段无关。

第三步,逐跳 TTL。

用 traceroute 看路径在哪一跳断掉。

bash 复制代码
$ traceroute -n 10.20.30.40

1  192.168.1.1    1.2 ms

2  10.0.0.1       3.4 ms

3  * * *

预期看到一串路由器地址。哪里开始出现 * * *,故障就在那一跳之后。注意 * 也可能是设备禁用了 ICMP 回应,不一定是断,要结合下一跳是否继续有回应判断。

第四步,NAT 转换。

如果路径跨了 NAT,确认表项是否存在。

bash 复制代码
$ conntrack -L | grep 10.20.30.40

预期看到一条对应的映射。如果表里没有,说明包根本没触发 NAT,可能被防火墙拦在了 NAT 之前。

第五步,回程路由。

去程通、回程不通是最隐蔽的一类问题。在对端或路径末端确认回程路由指向正确。

bash 复制代码
# 在对端主机上

$ ip route get 203.0.113.5

预期回程指向 NAT 设备的公网地址。如果对端没有回程路由,或者回程走了另一条不经过 NAT 的路径,回包就会丢失。

这五步走完,基本能定位故障发生在哪一跳、哪一层改写上。每一步都对应一个具体的"谁改了什么",而不是笼统地"网络不通"。

结尾

跨网段通信的复杂度,全部来自"每一跳只做局部决策,却要拼出端到端连通"这个设计。MAC 逐跳重写、IP 端到端不变、TTL 防止环路、NAT 打破端到端假设------每一条规则都在解决一个具体问题,也都在引入新的约束。

拿到一个不通的地址,判断方法只有一句:先算出源和目的两端各自的下一跳是谁,再看这条链路上有没有设备会改写地址。下一跳算错,问题在本机路由或 ARP;下一跳对但包不回,重点查回程路径和 NAT 表项。把"哪一跳、哪一层改写"定位清楚,剩下的就是查配置。

相关推荐
dadaobusi1 小时前
学习: Triton
学习
Moyu_Friday1 小时前
网络安全基础-计算机网络应用与协议
网络协议·计算机网络
workflower1 小时前
超算中心聚焦高性能计算场景(HPC)
大数据·网络·人工智能·机器人·云计算·汽车
miao_0v01 小时前
10.20任务
学习·个人开发
H.莓飛1 小时前
【Linux】自主 Shell
linux·运维·服务器·c语言·开发语言·visual studio
bigcarp1 小时前
Frida-dexdump 绕过app的Root检测
linux·运维·服务器
Nebula_g1 小时前
SpringBoot企业级实战项目—云禾外卖—SpringBoot常用注解解析
java·网络·数据库·spring boot·后端·spring·javase
Generalzy2 小时前
Qt 桌面开发入门:PySide6 学习路线、架构原理与技术选型
笔记·python·学习
草莓熊Lotso2 小时前
【C++ 基于微服务的即时通讯系统】Elasticsearch 从安装到 C++ 客户端二次封装全落地
linux·网络·数据库·c++·elasticsearch·微服务