1    Open×××简介
    ×××替代昂贵的专线用以在开放的Internet上实现了一个虚拟的网络,该虚拟网络本身在不安全的真实网络上对数据提供安全保护。
    Open×××实现了一个灵活的×××,和通过修改协议栈而实现的基于IPSec的×××相比,Open×××有以下的优点:
1.    Open×××无需对协议栈进行任何修改,无需专门的策略来解决×××数据穿越NAT的问题,因此可在现有的网络进行规划;
2.    Open×××使用虚拟网卡和路由进行虚拟网络的构建,配置十分方便;
3.    Open×××使用SSL协议对虚拟网络提供保护,从而实现“专用”,而SSL提供了丰富灵活的安全特性;
4.    Open×××的push模式可以最大限度简化客户端配置,服务器和客户端可以不必花费太多的精力来使得两端一致。
Open×××实际上是虚拟网卡设备,TCP/IP网络技术,路由技术,SSL结合而成的一个应用,前三者构建了虚拟网络—隧道连接的网络,最后SSL保证了虚拟网络通信的安全—隧道通信的认证和加密,因此使用Open×××的过程基本就是对上述四方面进行配置的过程。
2    Open×××的参数集以及配置实例
2.1    参数详解
Open×××拥有很多的参数,但是很多参数涉及到很多的细节,首先如果不考虑过多的细节,那么这些参数大致可以分为五类,其中有一些参数是为了配置方便,组合其它的参数而成的:
2.1.1    虚拟网卡配置参数:
1)    --dev tunX|tapX:配置虚拟网络使用的网卡设备,X是一个数字表示网卡的编号,在Unix/Linux系统中,它是一个字符设备,在Windows中,它是一个设备命名空间中的一个节点,tun设备和tap设备的区别在于出入前者的第三层(IP)数据报,而出入后者的是第二层(以太网)数据帧。
注意:tap设备是二层设备,tun设备为三层设备,此二者各有优劣,简述如下:
tap特点:
a)    应用这种设备可以复用任意的三层数据报;
b)    构成一个两路层网络,比如以太网,因此广播数据可以自由跨隧道传输;
c)    无需路由即可进行节点通信;
d)    配置简单,但是缺乏灵活性,IP层的优良特性无法自由应用。
tun特点:
a)    可以应用IP层的所有特性,比如Routing,IP-Qos,IP-fragment/de等,但是只支持IP数据报;
b)    构成三层网络,节点间如不在一个子网要路由;
c)    三层虚拟网络的每个子网下面没有链路层承载(ip数据报直接导出),因此链路层特性无法应用,比如以太网广播无法跨隧道传输,因此此虚拟网络是无法指定网关的。
2)    --dev-type dt:指示虚拟网卡设备的类型,仅仅在—dev参数无法识别设备类型的时候使用。
3)    --dev-node node:任意节点node被指示为虚拟网卡设备,node的路径以及名称可以任意,但是如果不是tunX/tapX的形式,那么必须配置—dev-type参数。
4)    --lladdr hw:为虚拟网卡配置链路层地址。
2.1.2    网络配置参数:
1)    --local host:配置本地使用的IP地址,如果不是为了bind,那么可以不配置此参数,Open×××会自行处理。
2)    --remote host [port]:用于client端,配置client连接的server的IP或者主机名以及port,该参数可以配置多个用以实现一定的冗余,client则按照配置顺序依次连接server,直到连接成功为止。
3)    --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的结果。
4)    --connect-retry n:配置连接重试的次数,仅仅对于—proto参数为tcp-client时有效。
5)    --connect-timeout n:连接重试的间隔。
6)    --auto-proxy:
7)    –bind:
8)    –nobind:
9)    --link-mtu n:配置四层链路的MTU,同时用同样的数值配置设备的MTU。
注意:这个配置可能会引起莫名其妙的问题,就其本质是由于Open×××不允许通过隧道的数据被任意分段,即使是IP分片也必须妥善处理,Open×××用一种规则的方式来收发socket数据。IP层将数据路由到虚拟网卡前如果发现数据报的长度大于虚拟网卡的MTU,那么就会将数据报分片,如果×××两端的虚拟网卡的MTU设置不一致,Open×××接收socket数据的时候就会产生问题,因为最一般的情况下Open×××调用recv/recvfrom的时候,参数中的len总是设置成自己的link-mtu,假设两端H1和H2的link-mtu分别为L1 和L2(L1>L2),H1端发送数据给H2,则数据在H2则会接收不完整,因此势必会产生错误,即使解密收到的数据可能正确,由于数据长度不一致,校验时也会出错,即使在没有校验的情形下,由Open×××发送给虚拟网卡的IP数据报也会不完整。因此最好不要配置link-mtu参数,让它默认值好了,如果非要配置,保持两端一致。可以在linux上用strace,tcpdump以及Open×××源代码确认以上问题,具体为何这样设计还不清楚。如果在不考虑安全因素(程序输出的意思是防止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留默认比较好。
10)    –tun-mtu:注意事项同上,但是要强调和link-mtu之不同,此二者配置一个即可且只能配置一个,此中之缘由在于其关联性,tun-mtu为虚拟网卡之mtu,而link-mtu则为链路的mtu,其大小相差一个固定长度,二者区别等同于TCP的MSS和物理链路的MTU之区别。
11)    –shaper:该参数对隧道的带宽进行了限制,主要用于建立多个通道时在各个通道进行策略化带宽分配,如果只建立一个通道,也就是说仅仅运行一个Open×××实例的话,这个参数就目前版本而言意义不大,因为本身单进程单线程的Open×××速度就很慢,再限速更没有意义了。
12)    –txqueuelen:该参数设定虚拟网卡的最大排队包的数量,也就是队列长度,默认为100,对于Open×××这样很慢的×××来说,100就够了,即使你设得再大,用户空间的Open×××进程处理不过来还是白搭。
2.1.3    路由参数:
1)    --route network [netmask] [gateway] [metric]:增加一条路由。
2)    --max-routes n:
3)    --route-gateway gw|'dhcp':
4)    --route-metric m:配置路由的传输开销
5)    --route-delay n [w]:配置路由添加的延迟,这是为了解决一IP地址自动配置的问题,有时候Open×××要添加路由了,可是OS还没有准备好,所以要给与一定的延迟。具体来说就是,client和server的连接建立以后,server需要往client端push一些信息,包括虚拟网卡ip地址,子网掩码等必须的信息以及路由等可选信息,client接收到以后需要在本机做相应的配置,比如配置虚拟网卡的ip/子网掩码,添加路由等,并且Open×××对虚拟网卡的管理采取了一种懒惰的方式,也就是对于server只有在Open×××起来,对于client只有在和server的连接建立的时候才会建立虚拟网卡对象并初始化,这个初始化过程就包括了设置ip地址/子网掩码,这个过程完成之前,添加路由是失败的,因此必须提供一些延迟保证虚拟网卡初始化完毕之后再添加路由。这个选项主要针对某些虚拟网卡驱动设计不友好的系统,比如基于NDIS驱动的windows系统,虚拟网卡的ip地址“看起来”是通过DHCP来分配,而DHCP分配是需要花费时间的,此间添加路由的请求必须等待。

2.1.4    SSL及安全参数:
1)    –genkey:生成一个对称密钥,该参数只能单独使用,该对称密钥的生成是为了使Open×××两端共享,从而不再使用SSL握手协议进行密钥协商。
2)    --secret file:使用共享的对称密钥。这实际上省去了SSL的握手,用于通信双方确信已拥有绝对保密绝对安全的对称密钥的情况,实际上SSL的握手也是为了这个保证,二者殊途同归,因此问题的难度就在于一端用—genkey生成的密钥如何传输至另一端。这其实就是另一个大问题了,可以用数字信封传过去,甚至可以冒险用明文直接发过去,这些都不是Open×××要考虑的。一般地在都是通过scp程序传递的,方便又安全。
3)    --reneg-XXX:这一族参数用来重新协商session key。Open×××基于SSL协议,然则其用法另有说法,SSL协议自带认证和加密功能,对于Open×××来说此二者是分开的,如果不考虑认证,密钥协商的过程很容易受到中间人攻击,因此不管是基于IPSec这种修改或挂钩协议栈实现的×××还是Open×××都提供了认证机制。Open×××使用证书来进行认证,使用DH来进行密钥协商。使用DH而不是别的是由于建立的隧道安全参数要每隔一段时间进行一次重新协商,默认时间为1个小时,而DH的效率很高,它不像RSA产生密钥那么耗时(由于美国出限制或者证书中没有可用于加密的公钥,在SSL握手时就需要临时生成一对RSA密钥),如此在server key exchange message消息中传输的就是DH参数了。
4)    –ca file:CA证书,用以验证对端的用户证书,这个参数可以包含多个证书,也就是一条证书链,在Unix/Linux中可以用cat命令将多个证书追加成一个文件。
5)    --cert file:自己的证书,用于传递给对端以表明自己的身份或者实现其它的准入性验证。
6)    --key file:对应—cert参数的key文件。
7)    --cryptoapicert select-string:用于从Windows的证书Store中获取证书,如此就不再需要—cert和—key参数了,select-string是一个字符串,可以认为是一个查找“键值”,以”名称:值”的形式存在,比如使用颁发给名字是“老李”的个人的证书,那么select-string就是:”SUBJ:老李”。这个参数多数用于key或者证书无法单独导出的环境下,比如一些设备。
2.1.5    事件参数:
1)    --route-up cmd:cmd是一个shell脚本,并且可以携带参数,该脚本在所有路由被添加之后被执行。
2)    --route-noexec:
3)    --route-nopull:用于client端,如果client端配置了—pull,那么该参数保证client不会pull过来server端push的路由。
4)    --allow-pull-fqdn:
5)    --ping n:每n秒钟ping一次对端。
6)    --ping-restart n:如果n秒收不到对端的ping报文,那么就重启。
7)    --ping-exit n:如果n秒收不到对端的ping报文,那么就退出。
8)    --keepalive n m:该参数项为5)和6)的封装,展开后有下面的形式:
if mode server:
ping n
ping-restart 2*m
push "ping n"
push "ping-restart m"
else
ping n
ping-restart m
注意:ping/ping-restart实现了一个心跳保活机制,这个机制在tcp协议构建的隧道和udp协议构建的隧道中表现是不同的,使用tcp建立的隧道本身就是有连接的,如果一端进程异常退出,那么OS必然会发送reset报文,这种情况其实keepalive的意义并不大,但是如果是诸如网线被拔掉或者断电之类的问题,keepalive就有必要了,因为tcp没有机会在事前发送任何报文,此时tcp隧道的keepablive配置就和udp一样了,如果ping-restart配置的过于大,对于tcp,将有更多的机会依靠tcp本身的超时重发机制使连接保持(前提是机器启动了或者网线又插上了),而对于udp,只能依靠这个ping-restart,否则它永远不知道对端已经断开过了,如果对端在ping-restart配置的时间内重新启动了,本端将不会知道对端已经断开过又重新启动了。反过来如果ping/ping-restart配置过小,将会有大量的ping包出现在网络,并且会引起大量的重连,有时由于网络拥塞导致的ping接收超时这种根本就不需要重连的情况也会重连。到底配置成多少需要根据自己网络的规模,使用的协议等因素来权衡。
9)    --persist-tun:在由于接收到SIGUSR1信号或者由于keepalive超时(--ping-restart)原因而重新建立连接的时候,并不关闭并且重新打开虚拟网卡设备。该参数会对网络行为造成影响,如下一例:client端由于某种原因休眠或者待机了,或者在任何可能的原因下重置了网络而没有结束Open×××进程,×××的路由随着网络的重置可能就被删除了,此时如果不重新打开虚拟网卡,待重新连上server之后,server端push过来的路由将不会被添加到client端主机路由表,最终之结果就是client端看到×××网络仍然连通,然则由于没有server端推送的路由而无法实际进行通信,×××网络仍然连通全系一条协议栈自动发现的链路层路由,而该路由只要虚拟网卡up,就会自动添加,如果用户不检查路由表或者非技术人员不懂的检查路由表,这种问题很难被排除。
10)    –persist-XXX族:以上描述了--persist-tun以及它可能造成的问题,但是参数存在的意义并不是为了引起问题的,相反是为了解决一系列问题的,之所以存在persisit参数,顾名思义是为了“保持”一些东西,也就是说这些配置在由于接收到SIGUSR1信号或者由于ping-restart而重置连接时,这些配置不会改变,这才是这一族参数存在的意义,比如管理员由于某种原因想热重启一下×××服务,或者由于网络拥塞而导致keepalive超时,重新分配IP地址,重新配置路由等动作都是没有必要的。和该族参数有关联的一个参数就是—user参数。
11)    --client-connect cmd:只要有client连接,服务器就会执行cmd。
2.1.6    Open×××本身的配置参数:
1)    --nice n:设置×××进程的nice值,此参数将影响×××进程的优先级,数值越小优先级越高。
2)    –user user:切换Open×××运行的UID。User是一个UID。这个参数的目的在于安全性,一旦有攻击者获取了Open×××进程的控制权,他所能做的也很有限。但是注意这种主动放弃root权限的行为是不可逆转的,否则攻击者也可以逆转,该参数也就失去了存在的意义。所以要注意的是,如果使用了—user参数,那么要确保一系列persist参数被设置从而在软重启Open×××的时候避免进行需要root权限的操作,这是因为很多操作是需要root权限的,比如ifconfig。
3)    –mlock:该参数的行为和操作系统紧密关联。配置该参数的结果就是将key等敏感信息锁在内存而永不被换入磁盘,如果不过度专注于其对效率的提升,则仅从安全性考虑它的意义也是不容忽视的。如果key被换入了磁盘,那么key就有可能被窃取,攻击者只需要简单的从交换分区读取即可,这里的key并不是指key文件,而是动态生成的用语协商密钥的数据,key文件一般以映射方式进入内存编址空间而不会被换入交换空间,如是可以说,key文件本身也是不安全的,攻击者掌握了你的机器,读取你的磁盘易如反掌,因此key文件的保护应依赖于你的主机安全,对于锁在内存的数据的保护,更进一步,应依赖于比主机安全更进一步的内存管理的安全,即使这样也还是可以攻破的,比如如果你有root权限,在很多linux上你都可以读取/dev/mem来dump当前的内存,这和读取磁盘有何区别呢?dump内存真的比dump交换分区更难吗?实际上一旦你有了root权限加上你有扎实的功底,整台机器的任何物件都尽在你的掌握之中。(最好的办法就是永远不将敏感数据导出到“公共”空间,我说磁盘和内存都是可以复用的公共空间,公共空间的不安全性在于大家都可以访问,充其量出示一些证明就能被准入,而这证明之真伪连带验证之逻辑本身又是一个极其复杂的体系,因此最好在私密空间完成所有的涉密操作,比如PKCS#11中所描述的,私密空间导入导出的仅仅是操作之结果以及操作之把手—句柄,正如开动汽车只需钥匙,按钮,方向盘而无需了解轮轴,气缸,变速器一致。)
4)    --mode m:配置×××的mode,有p2p和server两类,p2p顾名思义就是点对点,也就是说整个虚拟网络只有两台主机,这两台主机一台是服务器另一台是客户端;server的含义和p2p不同,它可以实现一个一对多的虚拟网络,同时可以有多个客户端连接入一个服务器。
5)    --topology t:2.1版本新增参数,指定网络拓扑,可选参数为net30,p2p以及subnet。net30拓扑结构主要用于p2p网络,该种网络假设server和client均存在于一个p2p链路之上,因此各需要配置一个p2p网关,每对连接要用去4个IP地址,这也正是30的含义,client和server均占有一个30位的子网掩码的网络,每端余下4个IP地址,然则一个子网中全0号IP表示子网本身,全1号IP表示广播地址,于是仅留下两个IP地址可用,net30虽然用于p2p链路,在以太网这种广播网络中也是可以使用的,该模式十分浪费IP地址;p2p拓扑用在一种半p2p的网络中,server将分配且只分配一个IP地址给client端,如此一来client端就可以和server端直接建立联系,而不必再通过一个p2p网关了,而server端仍然保留p2p网关,类似net30模式;subnet拓扑则是完全实现了虚拟网络从p2p模式向广播型网络的过渡,server端和client端均使用且只使用一个IP地址,如此一来server和所有的client构成一个可以基于广播链路的虚拟局域网(注意不是VLAN),大量节省了IP地址并且降低了配置的难度。
注意:net30->p2p->subnet的演进过程有两层含义,第一层含义代表了Open×××本身的版本升级,在2.0之前Open×××是不支持multi-clients的,也就是说所有的p2p模式以及c/s模式均是一对一的连接,因此虚拟网卡的配置很简单,直接配置上对端的ip地址即可,但是到了2.0及之后,Open×××的一个server可以对应多个客户端了,按照前面的思路,只需要将client虚拟网卡的地址p2p地配置成server的虚拟网卡地址即可,可是对于Windows这样却不行,于是就引出了第二层含义,为了兼容Tap-WIN32驱动,Tap-WIN32驱动不支持在“点对多点/多点对多点”的链路上创建p2p连接,于是不得不用net30的方式来“模拟”出一条p2p的链路,比如对于client来讲,tun的地址配置是:
10.8.0.6<-->10.8.0.5
server的配置是:
10.8.0.1<-->10.8.0.2
在这里,.5和.2地址都是为了模拟p2p的,对于client来讲,.5就是server,而对于server来讲,.2就是client,所以这些模拟的地址仅仅是在Open×××内部使用,对于外界是不可见的。这样在不支持真实p2p配置的Tap-WIN32上就可以配置出一条虚拟的p2p链路了,而模拟的地址仅仅作为路由将数据导向真正的server。
6)    --push "option":推送一个配置到对端,如果对端期望接收并应用该配置,那么它必须配置—pull参数。
7)    --client-to-client:使能客户端之间的通信,仅在—mode配置成server的时候有意义,此时server被众client连接,server就是这些client之间通信的“路由器”。
8)    
察看Open×××的man手册或者直接执行—help,你会得到另外一种参数分类的方式,基本上—help显示出的结果是基于Open×××的设施进行分类的,而本文档则是基于Open×××的行为进行分类的。
2.2    Open×××的配置实例
Open×××的使用非常简单,它有两种使用方式,这个和*nix的传统相一致,一般提供众多命令行参数的程序都会提供一个配置文件,因此如果环境过于复杂,那么用配置文件要比直接使用命令行更方便。