OpenVPN 的握手协议分析

又是好久没来这儿了啊。

最近因为工作需要(其实也没需要那么多),一直在断断续续地看 OpenVPN 的代码,终于大概搞清楚了它的握手是怎么个流程了。简单来说的话其实非常的简单,首先在 reliable 模块中实现了一个可靠的 UDP 报文协议,就是加上超时重传和确认报文的功能;然后用该协议交换一个 Hard Reset 命令,开始握手;最后建立 SSL 对象,并且通过内存 BIO 在可靠 UDP 协议的基础上转发 OpenSSL 的握手协议报文,通过这个 SSL 连接交换 OpenVPN 自己的密钥。接下来就是用这些密钥,该干嘛干嘛了。

本来还写了好多,不过太乱,写不下去了,还是简单点吧。具体实现就是,首先在 key_state_init() 函数中,初始化了 sslssl_bioct_inct_out 这几个相关的成员, ssl 就是普通 SSL 程序里面的 SSL 对象, ssl_bio 是以 BIO 接口来读写这个 ssl 成员而准备的,tls_process() 中通过 ssl_bio 这个成员完成 OpenVPN 自己的密钥交换,完成从 S_STARTS_GOT_KEY 这几个状态的转换。而 ct_inct_out 则是这个 ssl 对象的后端,不是通常用的 socket BIO 对象,而是两个 memory BIO 对象,为的就是得到 ssl 对象要发给对端的密文,然后通过可靠 UDP 协议来转发。这样, tls_process() 的主要工作就是操作这几个对象,外加可靠 UDP 模块的那几个队列了。

这里的关键就是通过 OpenSSL 实现提供的 memory BIO 对象抓取密文包的功能。如果是在其他没有这个功能接口的语言实现或封装中,则可以使用 socketpair 这种方法来抓,我今天就试着改了一下 OpenVPN ,这种方法是可行的。具体就是把其中一个 socket 给 ssl 当后端,另一个 socket 就可以用来读写密文了。注意 socketpair() 调用中的 type 参数要加上 SOCK_NONBLOCK

不过在 Erlang 这个语言中,甚至就连 socketpair 这个调用都没封。搜了一下它的源码,才发现自己实现一个也非常的简单,怪不得。下面摘自 otp_src_R14B/lib/kernel/test/inet_sockopt_SUITE.erl 文件:

1
2
3
4
5
6
7
create_socketpair(ListenOptions,ConnectOptions) ->
    ?line {ok,LS}=gen_tcp:listen(0,ListenOptions),
    ?line {ok,Port}=inet:port(LS),
    ?line {ok,CS}=gen_tcp:connect(localhost,Port,ConnectOptions),
    ?line {ok,AS}=gen_tcp:accept(LS),
    ?line gen_tcp:close(LS),
    {AS,CS}.

至于前面的 ?line 是啥,咱就不需要关心了。

 
comments powered by Disqus