openvpn简述
前置知识
TCP/IP网络,IP路由
VPN与OpenVPN
VPN是一种实现虚拟专用网的技术。 典型的实现有IPSec,L2TP,PPTP等,各类OS都有相应的支持,开源的轻量级实现有OpenVPN。
OpenVPN
OpenVPN实现了一个灵活的VPN,和通过修改协议栈而实现的基于IPSec的VPN相比,OpenVPN有以下的优点:
- OpenVPN无需对协议栈进行任何修改,无需专门的策略来解决VPN数据穿越NAT的问题,因此可在现有的网络进行规划;
- OpenVPN使用虚拟网卡和路由进行虚拟网络的构建,配置十分方便;
- OpenVPN使用SSL协议对虚拟网络提供保护,从而实现“专用”,而SSL提供了丰富灵活的安全特性;
- OpenVPN的push模式可以最大限度简化客户端配置,服务器和客户端可以不必花费太多的精力来使得两端一致。
- OpenVPN实际上是虚拟网卡设备,TCP/IP网络技术,路由技术,SSL结合而成的一个应用,前三者构建了虚拟网络—隧道连接的网络,最后的SSL保证了虚拟网络通信的安全——隧道通信的认证和加密,因此使用OpenVPN的过程基本就是对上述四方面进行配置的过程。
数据流
OpenVPN 协议
OpenVPN连接建立
关键概念
虚拟网卡——OpenVPN最重要的概念
虚拟网卡可以看作是一个连接协议栈底层和用户态应用程序的一个管道,数据从虚拟网卡流出,进入应用程序,封装后发出,如下图所示:
IP路由
IP路由在OpenVPN中起着至关重要的作用,数据包之所以能被截获进而被加密,完全靠IP路由
OpenVPN服务端配置实例
#执行外部程序的安全等级
script-security 2
#虚拟网卡设备:tun封装三层IP数据报,tap封装二层以太帧,支持非IP协议
dev tap
#使用的封装协议:tcp与udp
proto udp
#SSL握手时的角色
tls-server
tls-cipher RC4-MD5
cipher BF-CBC
#心跳保活
keepalive 1 5
#模式:p2p-仅两端端点;server-一个server可以被多个client连接
mode server
#虚拟IP地址池
server 128.129.0.0 255.255.0.0
#本地侦听地址和端口
local 0.0.0.0
port 6119
#日志等级
verb 4
dh dh1024.pem.dh.conf
#配置证书链
ca sslvpn.cer
#配置用户证书
cert /kssl/RHVPNS/cfg/pem/cae0c1c4.dbcb1db54387be84651b8edbe753af7dc1b9e88a.pem
key /kssl/RHVPNS/cfg/pem/cae0c1c4.dbcb1db54387be84651b8edbe753af7dc1b9e88a.key
OpenVPN客户端配置实例
script-security 2
dev tap
#定义自己的角色为客户端
client
proto udp
#自己断开时通知服务端,仅在udp协议下有效
explicit-exit-notify
#连接服务端的IP地址以及端口
remote 192.168.40.249 6119
#如果SSL握手不成功,便退出
tls-exit
resolv-retry infinite
#并不bind本地IP地址,由IP路由自动选择
nobind
#允许服务端地址漂移(高级)
float
verb 4
#配置证书链
ca /kssl/VPN/cfg/SSL-DemoCA.cer
#配置本地用户证书
pkcs12 psbc.pfx
OpenVPN的事件
OpenVPN可以在发生某些重要事件时执行外部脚本,对于服务端,典型的事件有:
- 虚拟网卡启动:只需要在服务端的配置文件中加入up /root/up.sh,即可,一个例子如下:
#!/bin/bash
route add 1.2.3.4 gw 128.129.0.2
- 客户端连接:只需要在服务端配置文件中加入client-connect /root/conn.sh,即可,一个例子如下:
#!/bin/bash
echo “push route 12.12.12.12 255.255.255.255" >>$1
- 客户端断开:某一个客户端断开的时候,会执行,一个例子如下:
#!/bin/bash
#有大量环境变量可用,可通过env来查询
name=$(printenv common_name)
real_addr=$(printenv trusted_ip)
echo $(date) "client $name from $ real_addr disconnect" >> /kssl/RHVPNS/log/conn.log
客户端典型的事件如下:
- up,down同服务端
- 服务端推送的路由生效时:在客户端配置中加入route-up “/kssl/VPNC/bin/route-add.sh” 即可
在以上事件发生时,除了使用bash,python,php等脚本之外,还可以使用plugin,一个plugin就是一个so/dll,它初始化时注册感兴趣的事件,事件发生时调用其回调函数。
- OpenVPN简介
VPN替代昂贵的专线用以在开放的Internet上实现了一个虚拟的网络,该虚拟网络本身在不安全的真实网络上对数据提供安全保护。
OpenVPN实现了一个灵活的VPN,和通过修改协议栈而实现的基于IPSec的VPN相比,OpenVPN有以下的优点:
- OpenVPN无需对协议栈进行任何修改,无需专门的策略来解决VPN数据穿越NAT的问题,因此可在现有的网络进行规划;
- OpenVPN使用虚拟网卡和路由进行虚拟网络的构建,配置十分方便;
- OpenVPN使用SSL协议对虚拟网络提供保护,从而实现“专用”,而SSL提供了丰富灵活的安全特性;
- OpenVPN的push模式可以最大限度简化客户端配置,服务器和客户端可以不必花费太多的精力来使得两端一致。
OpenVPN实际上是虚拟网卡设备,TCP/IP网络技术,路由技术,SSL结合而成的一个应用,前三者构建了虚拟网络—隧道连接的网络,最后SSL保证了虚拟网络通信的安全—隧道通信的认证和加密,因此使用OpenVPN的过程基本就是对上述四方面进行配置的过程。
OpenVPN关键配置项
- –dev tunX|tapX:配置虚拟网络使用的网卡设备,X是一个数字表示网卡的编号,在Unix/Linux系统中,它是一个字符设备,在Windows中,它是一个设备命名空间中的一个节点,tun设备和tap设备的区别在于出入前者的第三层(IP)数据报,而出入后者的是第二层(以太网)数据帧。
注意:tap设备是二层设备,tun设备为三层设备,此二者各有优劣,简述如下
- tap特点:
- 应用这种设备可以复用任意的三层数据报;
- 构成一个两路层网络,比如以太网,因此广播数据可以自由跨隧道传输;
- 无需路由即可进行节点通信;
- 配置简单,但是缺乏灵活性,IP层的优良特性无法自由应用。
- tun特点:
- 可以应用IP层的所有特性,比如Routing,IP-Qos,IP-fragment/de等,但是只支持IP数据报;
- 构成三层网络,节点间如不在一个子网要路由;
- 三层虚拟网络的每个子网下面没有链路层承载(ip数据报直接导出),因此链路层特性无法应用,比如以太网广播无法跨隧道传输,因此此虚拟网络是无法指定网关的。
- –dev-type dt:指示虚拟网卡设备的类型,仅仅在—dev参数无法识别设备类型的时候使用。
- –dev-node node:任意节点node被指示为虚拟网卡设备,node的路径以及名称可以任意,但是如果不是tunX/tapX的形式,那么必须配置—dev-type参数。
- –lladdr hw:为虚拟网卡配置链路层地址。
网络配置参数
- –local host:配置本地使用的IP地址,如果不是为了bind,那么可以不配置此参数,OpenVPN会自行处理。
–remote host [port]:用于client端,配置client连接的server的IP或者主机名以及port,该参数可以配置多个用以实现一定的冗余,client则按照配置顺序依次连接server,直到连接成功为止。
–proto p:配置隧道的类型,可以是udp或者tcp,其中tcp必须指明是server还是client,而udp可以不区分server和client,因此p可以为udp,tcp-server,tcp-client。
注意:用tcp还是用udp构建隧道呢?默认是udp。任何有连接的协议在出现丢包时都会要求重发或者自动超时重发,为了避免因对网络带宽的未知或者网络拥塞而丢包从而导致端点重发,tcp实现了慢启动,滑动窗口以及加增承减等机制,不幸的是,以上机制仅可用于分层模型中的一层,在不同层次都实现得如此复杂就可能引起判断叠加从而使得上述机制无法做出最好的判断,比如用tcp建立的隧道,并且上面承载的又是tcp数据,那么一旦出现丢包,最终的端点以及隧道都要重发数据,这就导致了网络流量突然增大,后面的行为很难在短时间给出预测并采取措施。事实上如果隧道本身并不是非用tcp不可,那么最好使用udp,保证连接与否是最终终端的事,而不是隧道的事,如果说最终用户使用tcp,那么他自己就保证了连接,如果他使用udp,那么说明他不在乎是否有连接,因此隧道使用udp,相反隧道使用tcp建立的话,如果最终用户使用tcp已经保证了连接,隧道没必要再多此一举,如果最终用户使用udp,那么隧道的tcp就降低了用户连接的效率,抵消了他使用udp的结果。
- –connect-retry n:配置连接重试的次数,仅仅对于—proto参数为tcp-client时有效。
- –connect-timeout n:连接重试的间隔。
- –auto-proxy:
- –bind:
–nobind:
–link-mtu n:配置四层链路的MTU,同时用同样的数值配置设备的MTU。
注意:这个配置可能会引起莫名其妙的问题,就其本质是由于OpenVPN不允许通过隧道的数据被任意分段,即使是IP分片也必须妥善处理,OpenVPN用一种规则的方式来收发socket数据。IP层将数据路由到虚拟网卡前如果发现数据报的长度大于虚拟网卡的MTU,那么就会将数据报分片,如果VPN两端的虚拟网卡的MTU设置不一致,OpenVPN接收socket数据的时候就会产生问题,因为最一般的情况下OpenVPN调用recv/recvfrom的时候,参数中的len总是设置成自己的link-mtu,假设两端H1和H2的link-mtu分别为L1 和L2(L1>L2),H1端发送数据给H2,则数据在H2则会接收不完整,因此势必会产生错误,即使解密收到的数据可能正确,由于数据长度不一致,校验时也会出错,即使在没有校验的情形下,由OpenVPN发送给虚拟网卡的IP数据报也会不完整。因此最好不要配置link-mtu参数,让它默认值好了,如果非要配置,保持两端一致。可以在linux上用strace,tcpdump以及OpenVPN源代码确认以上问题,具体为何这样设计还不清楚。如果在不考虑安全因素(程序输出的意思是防止active attack)可以更合理一些的话,我觉得recv数据时要按照对方的mtu来接收,毕竟recv和send只是一个中间阶段,数据从对端虚拟网卡的字符设备出来后就被send了,然后在本端被recv,之后被write进本端的虚拟网卡字符设备,如果仅仅按照本端的mtu来接收,势必会有问题,就好像在物理层上将数据截断一样。(在隧道的一端ping另一端,如果mtu不一致并且ping包大于mtu的小者的话,一定不同,可是仅仅将两端作为中间隧道的话就不一定了,数据从主机A,经由隧道的起始Ts,到达隧道终点Te,最终到达主机B,如果Ts和Te的mtu中小者都比A和B的mtu的大者大,在不考虑复杂的分段情况下是可以通的)因此,一种“几乎总是正确”的配置方法就是将mtu配置成一个很大的值,要比已知的物理链路的mtu都大,这样一来不会出错,二来可以不必担心两端不一致,三来可以最大限度的发送数据,而不会因为隧道太窄的缘故而降低数据发送速率,如果不理解以上这些或者为了保险起见,还是将mtu留默认比较好。
- –tun-mtu:注意事项同上,但是要强调和link-mtu之不同,此二者配置一个即可且只能配置一个,此中之缘由在于其关联性,tun-mtu为虚拟网卡之mtu,而link-mtu则为链路的mtu,其大小相差一个固定长度,二者区别等同于TCP的MSS和物理链路的MTU之区别。
- –shaper:该参数对隧道的带宽进行了限制,主要用于建立多个通道时在各个通道进行策略化带宽分配,如果只建立一个通道,也就是说仅仅运行一个OpenVPN实例的话,这个参数就目前版本而言意义不大,因为本身单进程单线程的OpenVPN速度就很慢,再限速更没有意义了。
- –txqueuelen:该参数设定虚拟网卡的最大排队包的数量,也就是队列长度,默认为100,对于OpenVPN这样很慢的VPN来说,100就够了,即使你设得再大,用户空间的OpenVPN进程处理不过来还是白搭。
路由参数
- –route network [netmask] [gateway] [metric]:增加一条路由。
- –max-routes n:
- –route-gateway gw|‘dhcp’:
–route-metric m:配置路由的传输开销
–route-delay n [w]:配置路由添加的延迟,这是为了解决一IP地址自动配置的问题,有时候OpenVPN要添加路由了,可是OS还没有准备好,所以要给与一定的延迟。具体来说就是,client和server的连接建立以后,server需要往client端push一些信息,包括虚拟网卡ip地址,子网掩码等必须的信息以及路由等可选信息,client接收到以后需要在本机做相应的配置,比如配置虚拟网卡的ip/子网掩码,添加路由等,并且OpenVPN对虚拟网卡的管理采取了一种懒惰的方式,也就是对于server只有在OpenVPN起来,对于client只有在和server的连接建立的时候才会建立虚拟网卡对象并初始化,这个初始化过程就包括了设置ip地址/子网掩码,这个过程完成之前,添加路由是失败的,因此必须提供一些延迟保证虚拟网卡初始化完毕之后再添加路由。这个选项主要针对某些虚拟网卡驱动设计不友好的系统,比如基于NDIS驱动的windows系统,虚拟网卡的ip地址“看起来”是通过DHCP来分配,而DHCP分配是需要花费时间的,此间添加路由的请求必须等待。
SSL及安全参数
–genkey:生成一个对称密钥,该参数只能单独使用,该对称密钥的生成是为了使OpenVPN两端共享,从而不再使用SSL握手协议进行密钥协商。
–secret file:使用共享的对称密钥。这实际上省去了SSL的握手,用于通信双方确信已拥有绝对保密绝对安全的对称密钥的情况,实际上SSL的握手也是为了这个保证,二者殊途同归,因此问题的难度就在于一端用—genkey生成的密钥如何传输至另一端。这其实就是另一个大问题了,可以用数字信封传过去,甚至可以冒险用明文直接发过去,这些都不是OpenVPN要考虑的。一般地在都是通过scp程序传递的,方便又安全。
–reneg-XXX:这一族参数用来重新协商session key。OpenVPN基于SSL协议,然则其用法另有说法,SSL协议自带认证和加密功能,对于OpenVPN来说此二者是分开的,如果不考虑认证,密钥协商的过程很容易受到中间人攻击,因此不管是基于IPSec这种修改或挂钩协议栈实现的VPN还是OpenVPN都提供了认证机制。OpenVPN使用证书来进行认证,使用DH来进行密钥协商。使用DH而不是别的是由于建立的隧道安全参数要每隔一段时间进行一次重新协商,默认时间为1个小时,而DH的效率很高,它不像RSA产生密钥那么耗时(由于美国出限制或者证书中没有可用于加密的公钥,在SSL握手时就需要临时生成一对RSA密钥),如此在server key exchange message消息中传输的就是DH参数了。
–ca file:CA证书,用以验证对端的用户证书,这个参数可以包含多个证书,也就是一条证书链,在Unix/Linux中可以用cat命令将多个证书追加成一个文件。
–cert file:自己的证书,用于传递给对端以表明自己的身份或者实现其它的准入性验证。
–key file:对应—cert参数的key文件。
–cryptoapicert select-string:用于从Windows的证书Store中获取证书,如此就不再需要—cert和—key参数了,select-string是一个字符串,可以认为是一个查找“键值”,以”名称:值”的形式存在,比如使用颁发给名字是“老李”的个人的证书,那么select-string就是:”SUBJ:老李”。这个参数多数用于key或者证书无法单独导出的环境下,比如一些设备。