openconnect源码分析

架构

1.  支持哪些特性?

1.1 支持 anyconnect 和 nc 两种VPN协议,从 vpn_proto 中,可以看出整个程序的大致功能。

anyconnect (cisco): 包括 cstp 和 udp

nc (juniper network): 包括 oncp 和 esp

1.2 支持 win 和 unix-like 两种客户端。

1.3 支持 openssl 和 gnutls 两种协议。

目前使用的是GNUTLS

2.  代码架构

如本章1.1所示,VPN支持 anyconnect 和 juniper 两种VPN协议。篇幅所限,下文中只介绍了 anyconnect 协议的实现细节。

整个代码层级可以分为四层:

第一层(总的对外接口): library

第二层(主要过程,即认证和心跳): mainloop auth auth-common

第三层(细分的逻辑模块,包括应用层的通信协议和虚拟网卡管理): tun/tun-windows dtls cstp http http auth

第四层: 其他基础功能模块

library  主要的对外接口
auth 实现认证功能
auth-common 处理表单时,生成token的其他途径
mainloop 实现心跳功能
http 实现认证时,所涉及的HTTP通信功能
http-auth 实现认证时,处理HTTP--401状态码的几种手段
cstp

认证成功后,建立TCP连接,发送CONNECT报文,建立HTTP隧道

基于TCP的心跳

重连

dtls

 认证成功后,建立UDP连接,建立DTLS连接

基于UDP的心跳

重连

gnutls 为TCP协议提供TLS相关接口
gnutls-dtls 为UDP协议提供TLS相关接口
gnutls-pkcs12 提供证书校验相关接口
ssl

提供TCP、UDP连接接口

提供cmd_fd的监控接口

tun/tun-windows 提供隧道的建立和心跳接口
script 支持隧道建立时的脚本执行
lzs 提供lzs压缩算法的接口
名称
功能

认证

1.  认证概览

认证阶段涉及至少三次(如果密码输入错误,则不只三次)的HTTP请求。参见本章第三节

整个认证过程分为四个阶段:

阶段1: 第一次HTTP请求返回相应的form表单,包括分组选择和账号密码填写。参见3.1

阶段2:程序提示用户选择分组,之后进入第二次HTTP请求,本次请求中新增了分组选择。参见3.2

阶段3: 分组发送后,服务端继续返回表单,客户端提示用户输入账户密码,之后发送给服务端。参见3.3

阶段4:(可选)根据最后一次HTTP通信返回的profile-uri和profile-hash,对服务端的数据进行验证。参见2.3和3.3

2.  认证过程中的关键步骤

2.1 do_http_request

do_http_request 包括 HTTP-request 和 HTTP-response 两个过程。

HTTP-request

报文准备  
openconnect_open_https 负责HTTP连接的建立,在cstp_connect中也会被调用
ssl_write 发送报文
名称
功能

HTTP-response

process_http_response

处理报文的头部,并按照transfor-encoding读取body内容,通常为chunk。

connection正常为keep-alive,如果为close,则直接不再处理本次通信。

location用于保存重定向url,如果循环重定向或者超过三次重定向,则认证失败。

第三个函数参数 http_auth_hdrs 用于匹配401相关的报文。

gen_authoriztion_hdr 处理401 unauthorized,实现了四种http认证方式
handle_redirect 更新url并进入下一次http请求
名称
功能

2.2 openconnect_open_https

openconnect_open_https,负责HTTP连接的建立,在cstp_connect中也会被调用。

connect_https_socket

建立TCP连接,如果之前保存过对端的ip则直接连接。

verify_peer 被注册的回调,调用gnutls库实现客户端的证书验证
gnutls初始化 涉及gnutls会话的初始化,以及绑定到之前建立的tcp套接字
cstp_handshake 一个标准的select非阻塞连接场景
名称
功能


2.3 parse_xml_response

auth

三次http通信中,前两次都是“main”,最后一次是“success”

用于handle_auth_form中,检查到“success”则不再处理

session-token 即重连中使用的cookie
config

主要读取其中的profile-uri和profile-hash,配合obtain_cookie中的fetch_config进行二次校验

error 认证出错,则包含相应的错误信息
名称
功能


2.4 handle_auth_form

该函数会被调用两次:

第一次:处理选择分组,返回newgroup。对应三次HTTP请求的第一次和第二次。参见3.1和3.2

第二次:处理账户密码,返回OK。对应三次HTTP请求的第二次和第三次。(可以重复进入)参见3.2和3.3

注:opeconnect还支持四种token模式,具体没有深究。

3. 认证中涉及的三次HTTP请求报文 

 报文涉及到敏感数据,有兴趣的话可以自己使用openconnect参数自行打印HTTP报文。

连接

1.  建立CSTP连接

1.1  连接过程

cstp连接负责发送CONNECT报文,从而建立客户端到服务器之间的隧道代理。

请求发送成功后,服务器会返回cstp和dtls两种报文配置,用于之后的本地网络配置(虚拟网卡的ip、dns、netmask等)、通信配置(加密算法、压缩算法、dtls的通信端口、mtu等)、心跳配置(心跳间隔)等。

openconnect_open_https

参见2.2

start_cstp_connection

dtsl-sessionid 用于cstp重连过程中,与重连报文检查类似,当发现数据变化则直接放弃重连

名称
功能

 2.  建立DTLS连接

dtls_attempt_period 根据dtls_setup最后一个参数配置
DTLS配置

根据CONNECT返回的报文配置

udp_sockaddr

初始化UDP套接字相关属性

udp_connect 创建socket连接

start_dtls_handshake

一系列的DTLS初始化,并绑定套接字
dtls_try_handshake

建立DTLS连接

使用二分法探测MTU

名称
功能

  

心跳

1.  心跳概览

reconnect_timeout和reconnect_interval

根据mainloop函数参数配置

cmd_fd

win下为socket,mac下为pipe

用于消息线程与心跳线程之间通信

心跳循环

包含三种心跳

可通过cmd_fd终止

也会因为重连失败、捕获异常等终止

cstp_bye

发送服务端断开VPN连接
os_shutdown_tun

清理本地的网卡配置

名称
功能

2.  隧道

2.1  建立隧道

prepare_script_env

根据CSTP建立连接时返回的报文配置,将本地网络环境加入环境变量

socketpair

本地的UDP读写端口,用于父子进程间的通信

子进程

应用环境变量并执行vpnc脚本

openconnect_setup_tun_fd

即父进程保存的sockectpair文件描述符

设置为非阻塞,并加入监控

用于tun_mainloop中

名称
功能

2.2  tun_mainloop

隧道建立是win和mac区别点最大的地方。隧道建立成功后,会产生tun_mainloop。

tun_mainloop实现了客户端和网卡设备之间的通信,在TUN建立时会产生相应的网卡文件描述符。

WIN和MAC在文件描述符方面有着显著的区别:

win的tun_fd被赋值为tap网卡的文件描述符;

mac的tun_fd被赋值为socketpair,与子进程vpnc脚本之间互相通信。

3.  DTLS心跳

3.1  dtls的状态

DTLS_SECRET和DTLS_NOSECRET目前没有使用。

DTLS_DISABLED:如果被设置为这个状态,则不会有DTLS相关的任何操作。

DTLS_SLEEPING:当接收到上层传递的PAUSED命令(cmd_fd)时,处于这个状态。

DTLS_CONNECTING:start_dtls_handshake后处于这个状态。(详见第二章第二节)

DTLS_CONNECTTED:dtls_try_handshake后处于这个状态。(详见第二章第二节)

3.2  dtls心跳

dtls心跳分为四个步骤:

步骤1: 检查dtls状态,并执行相应操作;

步骤2: 检查是否有输入,如果有,则更新dtls_times.last_rx,检查包的内容并执行相应操作;

AC_PKT_DATA

DTLS接收到的数据包,在tun_mainloop中传递给tun_fd

AC_PKT_DPD_OUT

服务器要求客户端发送DPD包request

AC_PKT_DPD_RESP

服务器返回的DPD包response

AC_PKT_KEEPALIVE

服务器发送的keep-alive包

AC_PKT_COMPRESSED 接收到需要解压的UDP包
AC_PKT_DISCONN 服务端发起断开本次VPN连接 (只有CSTP处理)
AC_PKT_TERM_SERVER 同AC_PKT_DISCONN (只有CSTP处理)
名称
功能

步骤3: 调用keepalive_action检查是否需要执行探活操作;

KA_DPD

需要发送DPD包

KA_DPD_DEAD

检测到DPD超时,发起重连

KA_KEEPALIVE

发送keep-alive包

KA_REKEY

重连

名称
功能

步骤4: 转发TUN设备发送的数据至VPN服务器;

3.3  dtls重连

调用connect_dtls_socket。(详见上一章第二节)

4.  CSTP心跳

4.1  cstp心跳

CSTP心跳分为四个步骤:

步骤1: 同DTLS步骤1;

步骤2: 检查是否有需要被写入的包,如果有,则尝试写入;如果写入失败,则迅速判断是否需要重连;

步骤3: 同DTLS步骤3;

步骤4: 如果DTLS没有连接,执行DTLS的第四步操作;

4.2  cstp重连

CSTP心跳分为两个步骤:

步骤1: 调用cstp_connect建立连接:(详细过程见上一章1.1节)

步骤2: 调用调用vpnc脚本,传递“reconnect”参数。

 

猜你喜欢

转载自www.cnblogs.com/shaellancelot/p/9112571.html
今日推荐