赞
踩
同样的思路,请看一个关于在BSD系统优化HTTPS的介绍《Optimizing TLS for High–Bandwidth Applications in FreeBSD》。但是本文无意详述sendfile/splice/tee这个系统调用族,这些只是一个引子,我想就这个引子展开针对内核态TLS的讨论。本文的结构大致如下:
0.例行的感慨
1.介绍KTLS的原理
2.介绍如何把Dave的例子跑通
3.对KTLS简单的进行评价
4.另外一种替代的方案
-----------------------------------1.KTLS源码下载
我们从KTLS的github上把源码Downlaod下来,并且编译,其地址如下: https://github.com/ktls/af_ktls2.编译和加载
这个步骤我分为以下几个步骤,对于熟悉的人来讲,超级简单。3.KTLS实例下载
Dave顺便写了一个非常简单的使用KTLS进行记录协议封装的例子,基于OpenSSL的。这个例子的结构非常简单:.... // 等价的OpenSSL操作
/* Kernel TLS tests */
int tfmfd = socket(AF_KTLS, SOCK_STREAM, 0);
if (tfmfd == -1) {
perror("socket error:");
exit(-1);
}
struct sockaddr_ktls sa = {
.sa_cipher = KTLS_CIPHER_AES_GCM_128, /* 指定cipher suit*/
.sa_socket = server, /* 指定附着的TCP socket */
.sa_version = KTLS_VERSION_1_2,
};
if (bind(tfmfd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
perror("AF_ALG: bind failed");
close(tfmfd);
exit(-1);
}
EVP_CIPHER_CTX * writeCtx = ssl->enc_write_ctx;
EVP_CIPHER_CTX * readCtx = ssl->enc_read_ctx;
EVP_AES_GCM_CTX* gcmWrite = (EVP_AES_GCM_CTX*)(writeCtx->cipher_data);
EVP_AES_GCM_CTX* gcmRead = (EVP_AES_GCM_CTX*)(readCtx->cipher_data);
unsigned char* writeKey = (unsigned char*)(gcmWrite->gcm.key);
unsigned char* readKey = (unsigned char*)(gcmRead->gcm.key);
unsigned char* writeIV = gcmWrite->iv;
unsigned char* readIV = gcmRead->iv;
char keyiv[20] = {0};
memcpy(keyiv, writeKey, 16);
if (setsockopt(tfmfd, AF_KTLS, KTLS_SET_KEY_SEND, keyiv, 16)) {
perror("AF_ALG: set write key failed\n");
exit(-1);
}
memcpy(keyiv, writeIV, 4);
if (setsockopt(tfmfd, AF_KTLS, KTLS_SET_SALT_SEND, keyiv, 4)) {
perror("AF_ALG: set write iv failed\n");
exit(-1);
}
uint64_t writeSeq;
unsigned char* writeSeqNum = ssl->s3->write_sequence;
memcpy(&writeSeq, writeSeqNum, sizeof(writeSeq));
if (setsockopt(tfmfd, AF_KTLS, KTLS_SET_IV_SEND, (unsigned char*)&writeSeq, 8)) {
perror("AF_ALG: set write salt failed\n");
exit(-1);
}
memcpy(keyiv, readKey, 16);
if (setsockopt(tfmfd, AF_KTLS, KTLS_SET_KEY_RECV, keyiv, 16)) {
perror("AF_ALG: set read key failed\n");
exit(-1);
}
memcpy(keyiv, readIV, 4);
if (setsockopt(tfmfd, AF_KTLS, KTLS_SET_SALT_RECV, keyiv, 4)) {
perror("AF_ALG: set read iv failed\n");
exit(-1);
}
uint64_t readSeq;
unsigned char* readSeqNum = ssl->s3->read_sequence;
memcpy(&readSeq, readSeqNum, sizeof(readSeq));
if (setsockopt(tfmfd, AF_KTLS, KTLS_SET_IV_RECV, (unsigned char*)&readSeq, 8)) {
perror("AF_ALG: set read salt failed\n");
exit(-1);
}
....// 在tfmfd socket上的send/recv/sendfile,由tfmfd socket完成记录加密解密和协议的封装解封装,与其下的TCP/UDP socket交互。

我称赞KTLS的朴素原因可能源自于我对OpenSSL的偏见,这属于个人情感的范畴,就不做煽动之辞了。
随着安全越来越重要,加密通信再也不是一个额外的组件了,它会内化成网络协议的一部分,这可以从HTTPS的逐渐风靡看出来,“buffer安全”还是“传输层安全”,哪个是你的追求呢?请继续管中窥豹吧!
static struct proto_ops algif_tls_ops = {
.family = PF_ALG,
.....
// sendmsg/page 1.加密消息;2.通过TLS记录协议封装消息;3.发送到底层附着的TCP/UDP socket
.sendmsg = tls_sendmsg,
.sendpage = tls_sendpage,
// recvmsg 1.获取底层附着的TCP/UDP socket的数据;2.解封装数据;3.解密数据后拷贝到用户态缓冲区
.recvmsg = tls_recvmsg,
.poll = sock_no_poll,
};
static const struct af_alg_type algif_type_tls = {
// bing 创建底层crypto_aead基础设施
.bind = tls_bind,
.release = tls_release,
// setkey 在底层crypto_aead设施上设置密钥
.setkey = tls_setkey,
.setauthsize = tls_setauthsize,
// accept 获取一个OP socket用于实际的读写操作,并将以下的ops字段作为其操作回调集合
.accept = tls_accept_parent,
// ops 实际的OP socket的回调集合
.ops = &algif_tls_ops,
// name 在Frame socket进行bind的时候,可以通过这个name找到该algif_tls_ops,用于索引
.name = "tls",
.owner = THIS_MODULE
};

/* Kernel TLS tests */
// 首先创建Frame socket
int tfmfd = socket(AF_ALG, SOCK_SEQPACKET, 0);
// 创建一个索引结构体,用于查找已经注册进内核的af_alg_type
struct sockaddr_alg sa = {
.salg_family = AF_ALG,
.salg_type = "tls", /* this selects the hash logic in the kernel */
.salg_name = "rfc5288(gcm(aes))" /* this is the cipher name */
};
// 根据上述的sa建立Frame socket与af_alg_type的关联
if (bind(tfmfd, (struct sockaddr *)&sa, sizeof(sa)) == -1)
// 获取一个操作实际IO的OP socket
int opfd = accept(tfmfd, NULL, 0);
...// 以下OP socket的行为完全取决于af_alg_type中的ops字段指示的proto_ops回调集了。
这个工作与Dave Watson的工作非常类似,仔细研究了Dave的KTLS之后,我本来想花这个周末的时间重新再重构一下这个KOpenVPN了,但是显然感到无力,所以我退而求其次,我只是想基于AF_ALG+自定义af_alg_type来实现一个简单的HTTP协议,仅用来传输文件,大体逻辑非常简单,即通过sendfile将一个文件发送给一个AF_ALG OP socket,然后实现一个af_alg_type完成HTTP头的添加后再继续发给底层的INET socket。如果换一种方式,我也可以单独写一个AF_MYHTTP模块来用一个新型的socket更加直接地实现上述需求。
------------------------------------
在完成了这篇文章后,我的下一个挑战就是,2016年12月31日要在小小幼儿园的小区开阔场地开唱《新长征路上的摇滚》(这源自于几周前小小秋游时在路上我被疯子逼着唱了一首《假行僧》,然后幼儿园老师就非要让我在新年联欢会上再来一首摇滚...)...有点紧张,决定喝瓶酒再上!好在疯子可以陪我上去唱《One night in BeiJing》....有人陪我丢人,总比一个人丢人强吧...总之,加油吧!为了孩子,啥都可以付出,唱个歌丢一回人算个毛线啊!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。