本文转载自 骏马金龙
提醒:
- 网上大多文章在这方面的分析都是错的,认为是
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 节点



