大家好,我是二哥。
疫情期间,二哥在家远程工作了。因为每天都要通过VPN访问公司内部服务,二哥想起来,何不给大家介绍下VPN的工作原理呢?VPN协议有很多个,最典型的有IPSec和OpenVPN,这里二哥只聊OpenVPN。
通常我们在自己电脑上打开OpenVPN client,输入账号密码进行认证,成功连上服务器之后,就可以像身处办公室一样访问内部服务了。比如:
- 打开浏览器可以访问JIRA。
- 打开terminal可以用git提交代码。
- 通过proxy可以访问谷歌等服务。
老铁们想过这一切背后发生了什么吗?来吧,进入正题。
tun设备
我们一定见过了令人眼花缭乱的各种网络设备,有物理网卡,也有如veth和bridge这样的虚拟设备,现在又来了一个tun设备。
tun设备是什么呢?简单来说,它是一个虚拟网卡,但它被创建出来的目的主要是为了隧道(tunneling)用,这也正是tun名字的由来。
tun设备和我们之前聊过的eth不一样的是:它一端连着协议栈,另一端连着打开它的用户态应用程序。如图1所示,发往tun0的数据被转发到了VPN程序,而普通的eth0设备则将会将其送往这个网卡所连着的另一个设备。
图 1:tun设备和eth设备对比示意图
现在我们大概能猜得出:使用 tun设备的目的,其实是为了把来自协议栈的数据包,先交给某个打开了/dev/net/tun字符设备的用户进程处理。然后它再把处理过后的数据包重新发回到协议栈中。
下面这段话摘抄自网络上,我觉得是对物理网卡和虚拟网卡的一个很好的概括总结。时间久远,我也不记得原出处是哪里了:
对于一个网络设备来说,它就像一个管道(pipe)一样有两端:从其中任意一端收到的数据将从另一端发送出去。 比如一个物理网卡eth0,它的两端分别是内核协议栈和外面的物理网络。从物理网络收到的数据,会转发给内核协议栈,而从协议栈发过来的数据将会通过它发送给物理网络。 那么对于一个虚拟网络设备呢?首先它也归内核的网络设备子系统管理,对于Linux内核网络设备管理模块来说,虚拟设备和物理设备没有区别,都是网络设备,都能配置IP,从网络设备来的数据,都会转发给协议栈,协议栈过来的数据,也会交由网络设备发送出去。至于是怎么发送出去的,发到哪里去,那是设备驱动的事情,跟内核就没关系了,所以说虚拟网络设备的一端也是协议栈,而另一端是什么取决于虚拟网络设备的驱动实现。
很显然,对于tun设备而言,将接收到的数据发往应用层这个动作是在它的驱动层面实现的。代码位于drivers/net/tun.c。
隧道协议
聊完了tun设备,我们再来看看隧道协议。在文章《当vpc遇到K8s overlay》里,二哥借助刘超在《趣谈网络协议》中所举的从广州自驾海南游的一个例子来解释所谓隧道协议的概念。我把那部分拷贝在这里,省得大家跳转到那篇里面去查看。
如图2所示,隧道协议分为三部分:乘客协议、隧道协议和承载协议。
图 2:隧道协议示意图
从广州出发自驾游海南,你的车怎么通过琼州海峡呢?得用轮渡,其实这就是隧道协议。
在广州这边开车是有“协议”的,例如靠右行驶、红灯停、绿灯行,这个就相当于“被封装”的乘客协议。海南那面,开车也是同样的“乘客”协议。
那我的车如何从广州运到海南呢?这就需要你先遵循开车协议,将车开上轮渡,所有车都关在船舱里面,按照既定的规则排列好,这就是隧道协议。在大海上,你的车是关在船舱里面的,就像在隧道里面一样,这个时候内部的乘客协议,也即驾驶协议没啥用处,只需要船遵从外层的承载协议,到达海南就可以了。
在海上坐船航行,也有它的协议,例如要看灯塔、要按航道航行等。这就是外层的承载协议。
到达之后,外部承载协议的任务就结束了,打开船舱,将车开出来,就相当于取下承载协议和隧道协议的头。接下来,在海南该怎么开车,就怎么开车,还是内部的乘客协议起作用。
去程详解
好了,如果老铁坚持看到这里,那么与本文相关的基础知识你都有了。我们来看看它们是如何应用在VPN当中的。
老规矩,先上一张大图。图中左边是客户端,运行有OpenVPN client,右边是服务端,运行有OpenVPN server。
一般情况下,OpenVPN server由企业IT负责运维,在这张图中,它充当了gateway的作用,一方面它面向OpenVPN client,接收从client过来的请求,另一方面它将请求转发至企业内部的网络或者外部服务。这里的外部服务通常是客户端无法直接访问的,比如google.com。
图 3:浏览器通过VPN访问企业内部服务场景
①:假设二哥需要通过浏览器访问https://srv.company.com,它是运行在公司内部的一个服务,IP地址是10.119.210.110。我们知道这样的请求首先需要DNS的介入。如果你打开本地DNS的配置文件,会发现OpenVPN client已经预先在里面插入了不少A record,当然这些记录里面与domian相关的IP地址都是私有IP地址。这也就表示浏览器其实是在向10.119.210.110发起请求,当然浏览器才不关心这些。
②:除了刚才提到的A record,OpenVPN client还会在本机路由表中插入若干条路由信息,路由表项大致如下表所示。我们知道当请求经过协议栈的时候,路由表会决定该请求应该从哪个接口离开,又该去往何处,下一跳在哪里。
代码语言:javascript复制# route
Destination Gateway Genmask Flags Metric Ref Use Iface
10.119.0.0 10.80.25.4 255.255.0.0 UG 0 0 0 tun0
default 192.168.0.1 0.0.0.0 U 0 0 0 eth0
③:好了,到这里我们可以猜到这个请求被路由表决定该送往本机设备tun0。回忆前面tun设备部分所聊部分,当它收到这个包后,会毫不犹豫地直接发往运行于本机的应用程序OpenVPN client。
④:抛去细节不表,vpn client拿到请求数据包后,它干的事情就是将数据加密和封装。和IPsec VPN不同,OpenVPN利用OpenSSL进行数据加密。如果你不打算看细节的话,可以浏览《特洛伊木马-图解VXLAN容器网络通信方案》里所谈的VXLAN封装过程。虽然细节不同,但流程上可做借鉴。
⑤:vpn client接下来需要将加密和封装好的数据送往vpn server。这一次它是用UDP协议,访问的是服务端9112端口。
⑥:通过这一番操作,浏览器的请求现在以加密的方式在internet上传输。理论上如无秘钥,别人几乎没有可能解开这些加密的数据。
⑦⑧:按照常规的数据接收流程,vpn server会正常收到数据。当它用对称秘钥解密并拆封数据后,找到原始请求是要访问10.119.210.110。
⑨:此时它又扮演了反向代理的角色,向内部服务10.119.210.110发起请求。
好了,到此我们详细看完了客户端通过VPN向企业内部服务发起请求时所涉及到的具体过程。
回程概述
当vpn server从10.119.210.110拿到了数据后,它按IPsec VPN协议继续将其加密和封装并发回给vpn client。
很自然地,vpn client会收到这份数据。那么当它解密、拆封后需要做什么呢?
或许你猜到了,它通过已经打开的/dev/net/tun设备,往tun虚拟网卡写入解密拆封后的数据。可还记得上一步vpn client是通过读取tun设备从而得到浏览器发出的请求?这里,它通过向tun设备写入数据来让浏览器得到它想要的回复。
前面提到对于内核和协议栈而言,既然tun设备就是一个网卡,那么当它接收到数据后,就需要完全按照网卡接收数据的流程来处理。至于这分数据是从哪里来的,内核和协议栈可不关心。虚拟网卡的详细接收流程,二哥在文章《看图写话:聊聊veth数据流》用一张大图做过详细解释,这里就不赘述了。
我们大致可以想象得出从tun0接收到数据,再次穿过IP层(以及路由表)和传输层,并最终达到浏览器的场景。
写完这篇,二哥不禁再次感慨网络模型分层设计模式的强大之处。分层意味着解耦和可扩展,每一层只需尽好自己的职责并和邻层打好交道即可,丝毫不需要关心其它层的细节。而分层模式也使得底层可以用更多的方式来为上层提供服务,只要语义不变即可。从浏览器发出请求到它接收到服务端的回复,中间其实经历了这么多事情,而浏览器对此一无所知,应用层果然都非常好被欺骗。
下篇预览
你们看到这篇的题目就肯定猜到了:除了VPN篇,还有其它与tun设备相关的话题。是的,tun在基于flannel实现的K8s Overlay网络模型中也起到了至关重要的作用。咱们下篇约。
以上就是本文的全部内容。码字不易,画图更难。喜欢本文的话请帮忙转发或点击“在看”。您的举手之劳是对二哥莫大的鼓励。谢谢!