通过 openvpn 分析 tun 实现隧道的数据流程


本文转载自 骏马金龙

提醒:

  1. 网上大多文章在这方面的分析都是错的,认为是 openvpn 对数据进行额外的封装和解封。
  2. 但实际上,在不绕过内核的情况下,网络数据的封装和解封由内核负责,用户空间的程序无法对数据进行封装和解封。

OpenVPN 配置 IP 隧道为例,下图是一个简图 (而且是不正确的流程图),后面会详细分析每一个步骤。

openvpn

公网上的两个主机节点 A、B,物理网卡上配置的 IP 分别是 ipA_eth0ipB_eth0

在 A、B 两个节点上分别运行 openvpn 的客户端和服务端,它们都会在自己的节点上创建 tun 设备,且都会读取或写入这个 tun 设备。

假设这两个 tun 设备配置的 IP 地址分别是 ipA_tunipB_tun,再在 A、B 节点上分别配置到目标 tun IP 的路由走本机的 tun 接口,两者就成功建立了一条能互相通信的隧道。

这里详细分析一下隧道通信的数据流程。以 ping ipB_tun 为例,其整体流程图如下:

openvpn

通过 openvpn 发送数据

当 A 节点数据要通过隧道发向 B 节点时:

  • 用户空间执行 ping ipB_tun,ping 程序会请求内核网络协议栈构建 icmp 协议请求数据
  • 经过路由决策,该 icmp 协议数据要走 tun0 接口,于是内核将数据从网络协议栈写入 tun0 设备
    • 写入 tun0 设备之前,网络协议栈会对 icmp 请求数据进行封装,假设封装后的数据称为 data1
    • tun 是三层设备,所以 data1 中只封装 IP 头,不封装以太网帧头,其源和目标 IP 分别是 ipA_tunipB_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

通过 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 的另一端即内核的网络协议栈

以上完整流程如下图:
openvpn

  • 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_tunipA_tun),而不封装帧头
  • 内核将响应数据写入 tun0 后,openvpn 从中读取,读取后将其作为普通数据发送给 ipA_eth0,于是数据写入网络协议栈,内核协议栈再次对其封装,包括端口号、两个 eth0 的 IP 地址以及以太网帧头,最终通过物理网卡 eth0 发送出去到达 A 节点

本文不允许转载。
  目录