前言
为了了解 ip route
命令显示的信息有什么含义,以及它对 Linux 收发网络包的影响,我在网络上搜索了很多文章,但是这些文章多数都仅仅是按序描述每个字段的作用,没有通过具体的例子来加深印象。偶然间,在 Diego Assencio 大大的个人网站里发现了这篇文章,拜读之后感觉收获颇丰。
这篇文章给出了两个例子,第一个例子是常见的网络访问,第二个则是在 VPN 环境下的网络访问,通过阅读这篇文章,至少对于我而言有种茅塞顿开的感觉,尤其是后面 VPN 的例子,读完后就能更好地了解容器网络或虚拟机网络的实现方式。
所以本文尝试翻译 Diego Assencio 的文章,一方面做一个初次翻译的尝试,一方面备份在这里方便未来自己的阅读,侵删。
正文
这篇文章将会描述如何解读 Linux 系统中路由表的信息。所谓路由表其实就是一个包含了许多路由规则的表单,网络包会根据它的目的地址来选择使用其中的哪条规则。
为了更好地理解这篇文章描述的内容,读者必须先理解两件事情:CIDR 表示法(这东西以 <network-prefix>/<netmask-length>
的格式来声明一个 IP 地址的子网)以及最长前缀匹配算法(译者注:事实上,对 tun/tap 设备的了解也是必须的,这对于理解下文 VPN 的例子尤其重要)。如果读者目前还不了解它们,请先花一些时间来学习,然后再继续阅读本文。此外,我们接下来要描述的例子都基于 IPv4 网络,但是相关的概念对 IPv6 网络也同样适用。
在 Linux 系统上,主要有两个命令用于获取路由表信息:route 和 ip。本文将使用 ip 命令,因为它输出的内容比 route 命令更加易于解读。为了使用 ip 命令来显示操作系统中路由表的内容,请打开一个终端模拟器(terminal),然后运行下面的命令:
1 | ip route show |
这个命令的输出取决于机器的网络配置以及实际的网络拓扑。比如让我们来考虑一个通过无线网络连接到路由器以访问外部网络的机器,机器的 IP 地址为 192.168.1.100,而路由器的地址为 192.168.1.1,那么 ip 命令的输出就有可能如下:
1 | default via 192.168.1.1 dev wlan0 |
让我们从第二行开始解读这个输出。这一行表示“任何被发往 192.168.1.0/24 这个网络的包都会以 192.168.1.100 作为源地址,然后被 wlan0 这个设备发送出去”,192.168.1.100 这个地址是 DHCP 服务端为 wlan0 设备分配的地址。而剩下的部分则可能不那么有趣:proto kernel
表示这条路由是被操作系统内核在自动配置期间创建的;而 scope link
则表示在 192.168.1.0/24 这个网络中的目标地址都仅对 wlan0 这个设备有效。
而这个输出中的第一行则表示所有网络包的默认路由(即,当没有其他路由可以被使用时,网络包将使用这一条路由)。具体含义指网络包将被 wlan0 设备发送到默认网关(译者注:通常就是指家用路由器),而这个网关的地址是 192.168.1.1。
ip 这个命令的输入非常灵活,例如可以只输入命令的一部分,然后这个输入就会被 ip 命令自动在内部进行补全。举例来说,下面所有的命令实际上都是等价的:
1 | ip r s |
接下来让我们来考虑一个更复杂的例子,当设备连接到一个虚拟专用网络(VPN)时,所有网络流量都会经过一个加密隧道(tunnel)被发送到 VPN 服务端。我们以一个 OpenVPN 的网络作为例子,在这个例子中,我们有如下设备及其 IP 地址:
- tun0:192.168.254.10
- wlan0:192.168.1.100
- 路由器:192.168.1.1
- OpenVPN 服务端:95.91.22.94
一个网络包在被发往目的地的途中会经历如下的流程:
1 | 原始网络包 --> tun0 -加密后的网络包-> wlan0 --> 路由器 --> OpenVPN 服务端 -解密后的网络包-> 目的地 |
首先,一个虚拟网络设备(通常叫做 tun0)会被创建,然后一些路由信息会被加入到路由表中,这些信息引导几乎所有的流量经过 tun0 这个设备,在这里网络包会被加密,然后最终通过 wlan0 这个网络设备被发送到 OpenVPN 的服务端。
下面是一种可能的路由表输出,这个输出来源于一个已经连接到 OpenVPN 服务端的设备(也就是 OpenVPN 的客户端):
1 | 0.0.0.0/1 via 192.168.254.9 dev tun0 |
直接解释这个路由表中的所有细节显得有些单调乏味,所以我们将关注这些输出中的重点部分。请注意第二行:这个设备上的默认路由并没有发生变化。然而,第一行和第四行引入了两条新的路由规则,这将完全改变游戏的规则:被发送到 0.0.0.0/1 和 128.0.0.0/1 两个网络的所有网络包都会经过 tun0 设备,并且以 192.168.254.9 作为网关的地址。
这里需要注意的是,0.0.0.0/1 和 128.0.0.0/1 分别匹配目标地址的第一个比特位为 0 和 1 的网络包。当它们一起工作时,就可以代替第二行的规则成为新的默认路由规则。因为对于任何一个网络包而言,它的目标地址的第一个比特位不是 0 就是 1,而根据最长前缀匹配算法,网络包将优先选择这两条规则(译者注:可以认为 default 路由中目标地址子网掩码的长度为 0)。因此,当 OpenVPN 进程为主机创建了这两条路由后,所有的网络包都会默认被发往 tun0 设备,而从这里开始,网络包就会被加密发送到 95.91.22.94(OpenVPN 服务端的地址)。显而易见,上面输出中的第三行描述了这部分内容:被发往 95.91.22.94 的网络包都由 wlan0 设备以 192.168.1.1 作为网关发出。
一些读者可能会好奇上面的输出中 192.168.254.9 这个地址,那么它是怎么来的呢?事实上,OpenVPN 在创建 tun0 设备时是以 point-to-point 模式创建的,这意味着这个设备在工作时就好像直接连接在另一端上(译者注:也就是不需要通过中间路由器进行转发),而这个 192.168.254.9 就是另一端的设备,它实际上就是 OpenVPN 的服务端。服务端负责创建 192.168.254.0/24 这个虚拟网络,然后从地址池中选出空闲的 IP 地址分配给那些连接到自己的主机。如上面输出的最后一行所示,192.168.254.10 就是这个路由表所在的主机被分配到的地址,而 192.168.254.9 则是服务端在这个虚拟网络中的地址。
读者可以通过运行下面的例子来更清晰地证明上面的描述:
1 | ip addr show dev tun0 |
对于我们的例子而言,这条命令的输出可以非常清晰地展示前文所述的 point-to-point 连接(注意倒数第二行):
1 | 21: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 100 |
说明
这篇文章描述了两个路由表影响网络包收发的例子,如果读者不了解 tun/tap 设备,在阅读 VPN 部分时可能会比较吃力。
简单来说,这种设备可以由系统中的某一个进程打开,然后进程可以选择读取或写入这个设备,如果有网络包被发送到这个设备,那么进程就可以从中读取到数据,和常规的 socket 不同的是,tun 设备可以读取到 IP 层的内容,tap 设备则可以读取到链路层的内容。所以如果进程在收到网络包后将它包装在一个常规的 tcp 或 udp 包中,再经过物理网卡发送到外部网络的某台主机上,那么这台目标主机在解包后就可以看到原始的 IP 包或链路层的内容,从而好像内部的这个网络包直接到达了主机上,以此营造出源主机和目标主机在这个内部网络包层面是相互可达的假象,这种虚拟化技术被叫做 Overlay 网络,如今被广泛运用于容器或虚拟机技术中。
于是在上面 VPN 的例子中,192.168.254.0/24 实际上就是内部网络包的源地址与目的地址所属的网络,而 OpenVPN 的实际网络地址其实是 95.91.22.94,也就是说,如果没有 OpenVPN 创建的 tun 设备,主机通过正常的网络只能访问 95.91.22.94 这个地址。
其他
我的大学老师曾对我说,计算机领域要研究的内容总是离不开计算、存储和网络,这句话在近期深入学习容器技术的过程中不断出现在我的脑海里。 Diego Assencio 大大的原文中让我眼前一亮的其实就是 VPN 这个例子,它也是 flannel 项目最初实现的 udp 后端的核心原理,帮助我理解了更多网络方面的有趣知识。笔者对这篇文章的出现表示衷心的感谢。