本文转载自 骏马金龙
提醒:
- 网上大多文章在这方面的分析都是错的,认为是
openvpn
对数据进行额外的封装和解封。- 但实际上,在不绕过内核的情况下,网络数据的封装和解封由内核负责,
用户空间
的程序无法对数据进行封装和解封。
以 OpenVPN
配置 IP 隧道为例,下图是一个简图 (而且是不正确的流程图),后面会详细分析每一个步骤。
公网上的两个主机节点 A、B,物理网卡上配置的 IP 分别是 ipA_eth0
和 ipB_eth0
。
在 A、B 两个节点上分别运行 openvpn
的客户端和服务端,它们都会在自己的节点上创建 tun
设备,且都会读取或写入这个 tun
设备。
假设这两个 tun
设备配置的 IP 地址分别是 ipA_tun
和 ipB_tun
,再在 A、B 节点上分别配置到目标 tun
IP 的路由走本机的 tun
接口,两者就成功建立了一条能互相通信的隧道。
这里详细分析一下隧道通信的数据流程。以 ping ipB_tun
为例,其整体流程图如下:
通过 openvpn 发送数据
当 A 节点数据要通过隧道发向 B 节点时:
- 用户空间执行
ping ipB_tun
,ping 程序会请求内核网络协议栈
构建 icmp 协议请求数据 - 经过路由决策,该 icmp 协议数据要走
tun0
接口,于是内核将数据从网络协议栈
写入tun0
设备- 写入
tun0
设备之前,网络协议栈会对 icmp 请求数据进行封装,假设封装后的数据称为 data1 tun
是三层设备,所以 data1 中只封装 IP 头,不封装以太网帧头,其源和目标 IP 分别是ipA_tun
、ipB_tun
- 注意,data1 中没有封装以太网帧头
- 写入
- OpenVPN 读取
tun0
设备数据,将读取到的 data1 数据当作普通数据发向 B 节点的 eth0 地址ipB_eth0
,于是 data1 写入到网络协议栈- 用户程序
OpenVPN
从虚拟网卡
读取的数据是原封不动地 data1,其中包含了内核已经封装过的 IP 头,OpenVPN
是无法对数据进行解封的 OpenVPN
请求内核将 data1 作为普通数据发送出去,于是包含 IP 头的 data1 被写入内核网络协议栈
- 内核经过路由决策,data1 数据要从本机的 eth0 接口流出
- 所以
网络协议栈
会对 data1 进行封装,假设封装后的数据称为 data2 - data2 中封装的内容包括:
OpenVPN
的源和目标端口 (因为OpenVPN
是用户服务程序)- 两个节点的 eth0 的 IP 地址
- 以太网帧头 (因为数据要从物理层的 eth0 设备出去,所以要从四层封装到二层)
- 注意 data2 中有两层 IP 头,内层的 IP 头即 data1 中的 IP 头是 tun 设备的 IP,外层的 IP 头是物理网卡 eth0 的 IP
- 用户程序
- data2 最终通过 A 的
物理网卡
eth0 发送出去到达 B 节点的物理网卡
eth0
以上完整流程如下图:
通过 openvpn 接收收据
A 节点通过 eth0 发送的数据经由网络最终会到达 B 节点的 eth0 接口。
- 当 B 节点的物理网卡 eth0 收到数据后,对比特流进行解析,得到 data2 数据,写入内核
网络协议栈
:网络协议栈
会对 data2 解封,将以太网帧头、外层 IP 头和端口层剥掉,最终得到 data1- 因为目标端口号是 OpenVPN 程序所监听的,所以解封后的 data1 数据交给 OpenVPN,即内核将 data1 拷贝到
用户空间
- 注意,data1 中完整地包含了 A、B 两节点中两个 tun 设备的源和目标 IP
OpenVPN
程序得到 data1 后,发现目标 IP 是tun0
设备的 (虽然 openvpn 无法解封数据,但却可以分析数据),于是将 data1 数据从用户空间写入tun0
设备 (就像外界数据流入物理网卡
一样),data1 最终传输到 tun0 的另一端即内核的网络协议栈
中
以上完整流程如下图:
- B 节点的
网络协议栈
对 data1 数据解封得到最内层的 ICMP 协议请求数据,同时内核发现目的 IP 是配置在本机tun0
设备上的地址,于是响应它而非丢弃该数据。B 构建响应数据的过程类似于 A 节点构建 ping 请求时的流程- ICMP 协议位于 tcp/ip 协议栈中,不涉及应用层,所以直接由内核构建 ping 的响应数据
- 因为解封 data1 时的源 IP 是 A 节点的
ipA_tun
,所以构建的响应目标是ipA_tun
- 经过路由决策,该响应数据要从
tun0
流出,tun0
是 3 层设备,所以只封装 IP 头 (源 IP 和目标 IP 分别是ipB_tun
、ipA_tun
),而不封装帧头
- 内核将响应数据写入
tun0
后,openvpn 从中读取,读取后将其作为普通数据发送给ipA_eth0
,于是数据写入网络协议栈
,内核协议栈再次对其封装,包括端口号、两个 eth0 的 IP 地址以及以太网帧头,最终通过物理网卡
eth0 发送出去到达 A 节点