前几天GSoC的工作,了解了一下KTLS、ULP以及和OpenSSL的交互,踩了很多坑。同时因为技术比较新,几乎没有中文文档,英文的都很少,在此记录一下,也算是贡献一点中文资料吧……

什么是Kernel TLS(KTLS)

首先我们来看看一个使用OpenSSL的简单web server(摘自原论文):
1.png
目前的TLS加密一般是在用户态,也是由于各种历史原因和http的支持,都是先建立TCP通信,然后再在用户态进行握手等操作。比如上图发送一个文件,利用system call从硬盘读出内容,然后回到用户态,OpenSSL加密后再通过system call发出去,这一来一回需要进出内核态2次、伴随着文件内容也复制了2次,虽然硬盘访问占大头,但这个开销还是可观的。

为什么不在Kernel态实现OpenSSL?
好问题,参考这里,简单来说就是现在版本的OpenSSL使用了很多用户态独有的东西,加上历史包袱和各种重构使得这件事难度极大。

虽然通过mmap之类的可以避免一次复制,然而还是要进出内核,复制加密数据,Zero Copy的诱惑是巨大的,所以有了 sendfile 这个好东西,不熟悉的可以参考这里
但是TLS却用不了这个福利,因为加密是在用户态的, sendfile 只能发送原数据,所以Facebook才提出了KTLS的技术,原论文

简单来说就是下面这个图
2.png
既然OpenSSL无法放到内核态,不如单独把TLS加解密放进去,这样就可以利用 sendfile 达到零拷贝和不出内核了。

唯一的KTLS中文介绍在这里大家可以看看。

ULP

同样是上面那个大佬的博客介绍了ULP。总的来说就是一个专门针对KTLS的框架(当然也可以干别的)

一些技术性问题

然而很可惜,上面大佬接触是在16-17年,KTLS和ULP还不是很成熟,现在虽然基本原理没变,但是接口啥的现在都变了很多。原论文用的是GNUTLS,有一个API gnutls_record_get_state 可以拿到cipher的参数,但是OpenSSL没有这个,之前大佬写的这个OpenSSL的版本由于更新并不能跑起来,而且在Linux 4.14以后内置了KTLS,Beta版的OpenSSL也内置支持了,所以之前很多代码也跑不起来了,我们需要解决怎么用新的API……

如何在OpenSSL中使用KTLS

自己写可以参考Linux官方文档,如果要用Beta版的OpenSSL需要自己编译,由于没有文档中间的麻烦很多,通过读ktls相关的代码以及调试OpenSSL打印输出终于找到了正确的姿势:

  1. 确保Linux内核和Header版本>4.17,可以这么看 uname -a 应该>4.17, cat /usr/include/linux/version.h | grep LINUX_VERSION_CODE 应该>266496。注意如果用Docker也请再次确认,我在这里浪费了很多时间调试,总是不明原因返回-1…
  2. 确认 tls 内核模块开启,一般的发行版内核编译应该用的是 m 模式,可以检查一下 lsmod |grep tls,如果没有输出,请使用 sudo modprobe tls,确保运行 lsmod 命令可以找到 tls 模块
  3. 下载OpenSSL,master分支,编译:
    1
    2
    3
    ./config enable-ktls
    make build_sw -j4
    make install_sw

这样基本上是可以用的,如果要验证可以在 include/internal/ktls.h 第322行左右加上 fprintf(stderr, "[KTLS]%s[/KTLS]\n", msg.msg_iov->iov_base); 这种,如果连接中打印出来的是明文那就确认确实使用了KTLS,内核直接返回的明文。
3.png

注意目前版本KTLS只支持 TLS_AES_128_GCM_SHA256 的cipher,且只有TLS1.2有效,不过已经有了PR,如果合并了那可能就支持其他的了可以看看。

BUG

目前master分支kTLS存在内存泄露的BUG,具体表现是多次连接后就会时不时出现乱码甚至挂掉,提交了issuemailbox不过好像没什么人理……

参考资料