IP 封包到达主机后,OS 根据它的目的地址,以及所有监听的套接字,判断该封包是否是发给本机的,如果是发给本机的,接下来判断能否接收 (和 iptables, 防火墙等有关)。在 Linux 下,进程是可以监听非本机 IP 地址的套接字的,例如通过 TPROXY 机制。

如果不是发给本机的,并且操作系统不允许 IP 转发,则丢弃该 packet. 否则,接下来 OS 尝试转发该封包. 在转发前会交给 iptables/netfilter 处理。

接下来 OS 根据自身的路由配置 vrf, policy-based routing, 以及内存中的路由表,来决定应该由哪一个网卡(接口)负责转发它。对于常见的 PC 而言,大概率只有一个接口,而这个接口一般也是连接到默认网关,对于路由器而言,它分 WAN 口和(多个) LAN 口,此时路由就是确定要把它发往哪个口的过程。

路由的过程就好像是一个车开到了分岔路,它会根据它的目的地(类比为封包的目的 IP 地址),以及路牌的指示(操作系统以及操作系统的路由表)来决定该选择哪一条路。

路由确定后,OS 开始转发这个 IP 封包,要发送它就要将它拆解成以太帧 (frames),OS 需要确定并填写这些以太帧头部的源 MAC 地址和目的 MAC 地址,OS 默认会向网卡所在广播域广播 ARP/NDP 请求以尝试获取 IP 地址对应的 MAC 地址,而如果网卡的 ARP 处于关闭状态(例如在 Linux 中用 ip 命令行工具设置了 arp off),则需要用户手动配置 IP 地址对应的 MAC 地址,如果目的主机和当前网卡处于同一个广播域,那么即便是不知道对方的 MAC 地址,用 ff:ff:ff:ff:ff:ff 作为目的 MAC 地址也是可以的。

OS 有可能会利用缓存来加速 IP 地址到 MAC 地址的转换过程。

模拟两台物理直连机器的 IP 通信

我们可以用 Linux 提供的「网络命名空间」功能来模拟这样一个情形:一根 RJ45 线直接插入到两台电脑的网卡上,以这种方式直接连接,并且让这两台电脑交换 IP 封包。

我们首先用 multipass 启动一台虚拟机,该虚拟机不需要和宿主机桥接:

multipass launch --name foo

然后我们执行

multipass shell foo

进入到虚拟机的 shell 交互界面。

通过执行下列命令创建两个 netns(网络命名空间)对象:

sudo ip netns add ns01
sudo ip netns add ns02

接下来创建一对 veth:

sudo ip link add v1 netns n01 type veth peer name v1 netns n02

该命令在 n01 这个 netns 下创建了一个叫做 v1 的 veth 设备,并且让它和 n02 netns 下的同名 veth 设备直接连接(peer 关键字)。

这时我们用

sudo ip -n n01 link show