Openssl 握手 (SSL_accept) 以毫秒为单位缓慢

Openssl handshake (SSL_accept) is slow in milliseconds

本文关键字:为单位 缓慢 accept 握手 SSL Openssl      更新时间:2023-10-16

我正在用C ++编写服务器程序。 我将 epoll 套接字多路复用用于 Linux,将 OpenSSL 1.0.2 用于安全层

在 Centos 6.9 最终版(具有 4 个内核、2GB 内存、x64 架构)。

这个服务器程序的目的是为我的项目提供一个websocket/socket服务器库。

我的问题是, SSL_accept和SSL_do_handshake功能有点慢。

我为我的问题做了一些静态;

对于 50k EPOLL_CTL_ADD的使用(我的意思是,同时/重复 50k 用户连接)我的主要阅读 函数(SSL_read,SSL_accept逻辑)总共花费 92.8 秒。

对于这 92.8 秒,SSL_accept(或SSL_do_handshake [我尝试过])函数花费 81.9 秒。

但是当我关闭我的 SSL 算法(用定义制作)并重新强调时,读取主函数用法 [read( function] 在此静力学中使用相同的函数用法为 50k 花费 10.4 秒。

平均值是;

  • 没有 SSL:我的读取主函数从 epoll 循环 (10.4/50000) 每次读取调用(并执行我的上层函数)平均花费 0.2 毫秒

  • 使用 SSL:我的读取主函数从 epoll 循环 (92.8/50000) 每次读取调用(并执行我的上层函数)平均花费 1.85 毫秒

在这些条件下,我的服务器只能同时接受18~19k用户(我尝试了很多次)(SSL版本比非SSL版本慢9.25倍)。在这些时候有很多锁定我的服务器本身。

笔记:

  • 在同一个VPS上,我尝试了nodejs,nginx等,没有像我的服务器那样锁定自己。
  • SSL证书是从comodo购买的SSL。
  • 研究了数千次,我查看了很多服务器源代码。

我的问题是,为什么我的SSL_accept/SSL_do_handshake功能很慢。

我在3~5个月内尝试了很多东西。但是我想不通。

我花费时间的逻辑是:

###
timeMs = CurTime()
SSL_accept(...)
timeTotalVariable += (CurTime() - timeMs)
###

这是我的伪代码:

"call" ctx = SSL_CTX_new(SSLv23_method())
; close sslv2 & sslv3 protos
; set SSL_MODE_RELEASE_BUFFERS | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_OP_NO_COMPRESSION | SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
; set SSL_SESS_CACHE_OFF (if i use built-in openssl server cache, there is nothing big different)
; SSL_CTX_set_session_id_context ...
; SSL_CTX_set_verify(cx, SSL_VERIFY_NONE, NULL);
; SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
; SSL_CTX_set_tmp_dh(ctx, ...
; SSL_CTX_use_certificate_chain_file(...
; SSL_CTX_use_PrivateKey_file(...
; SSL_CTX_check_private_key(...
; SSL_CTX_set_cipher_list(...
; SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE);
; SSL_CTX_set_ecdh_auto(ctx, 1); (or set curve with "prime256v1")
"call" bind(srvSock, ...
"call" srvSock = listen(...
"call" add_epoll(srvSock, ReadFlag
###
"func" Read_Handle(Sock // from my epoll( loops
if (Sock == srvSock)
"call" Accept_Handle(Sock
else
"call" Real_Read_Handle(Sock
"end func"
"func" Real_Read_Handle(Sock
if (!SSL_is_init_finished(ssl)) {
timeMs = CurTime()
"call" ret = SSL_accept(ssl);
timeTotalVariable += (CurTime() - timeMs)
"call" err = SSL_get_error(ssl, ret);
if (err == SSL_ERROR_WANT_READ) {
set_epoll(Sock, ReadFlag
return;
}
if (err == SSL_ERROR_WANT_WRITE) {
set_epoll(Sock, WriteFlag
return;
}
...
}
"call" SSL_read(ssl, ...
...
"end func"
"func" Accept_Handle(Sock
"call" newSock = accept(Sock, ...
"call" MyNonBlockingSocketSetFunction(Sock
"call" ssl = SSL_new(ctx
"call" SSL_set_fd(ssl, newSock);
"call" SSL_set_accept_state(ssl
"call" BIO_set_nbio(SSL_get_rbio(ssl), 0);
"call" BIO_set_nbio(SSL_get_wbio(ssl), 0);
"call" add_epoll(newSock, ReadFlag // handshake will do at main read function.
"end func"

我的代码非常大(我在这个服务器上编码了很长时间),所以我给出了伪代码。 正如我所说,我尝试了很多事情,但根本没有成功。

斯特雷克斯有一个问题(我认为);

read(9898, "2633F", 5)             = 5
read(9898, "20BA4@/241|9325247351S2657<2045260203H\314212301324Bf353+"..., 70) = 70
read(9898, "24331", 5)            = 5
read(9898, "1", 1)                     = 1
read(9898, "2633(", 5)             = 5
read(9898, ">205203006354337264{21464343311Ai%3034720307300253+200}B\326:211"..., 40) = 40
write(9898, "263331243061,30017225!A4310312(231.26323t2653}211"..., 258) = 258
read(9896, "27331347", 5)          = 5
read(9896, "t()R2347pvE341n272C231267352.21G212u203wOv163264352205326340"..., 487) = 487
read(9895, "2633F", 5)             = 5
read(9895, "20BA4213335242OV21543713452321257217377236"xN206322205I{242307276"..., 70) = 70
read(9895, "24331", 5)            = 5
read(9895, "1", 1)                     = 1
read(9895, "2633(", 5)             = 5
read(9895, "^341Ii27335nMe214303352aE23626q333274366375255@;275Ad204ko223377"..., 40) = 40
write(9895, "263331243061,30017225!A4310312(231.26323t2653}d"..., 258) = 258
read(9894, "27331346", 5)          = 5
read(9894, "n+e16\330305322364367j356b3363T3va2003243107_320,22H333350"..., 486) = 486
read(9892, "2633F", 5)             = 5
read(9892, "20BA4Q214t7$276$3744247364,3203762252623z204254U35517323214S"..., 70) = 70
read(9892, "24331", 5)            = 5
read(9892, "1", 1)                     = 1
read(9892, "2633(", 5)             = 5
read(9892, "201214T8257nBM{210202V25340R315)320343'h3413533516f!$314230300221"..., 40) = 40
write(9892, "263331243061,30017225!A4310312(231.26323t2653}h"..., 258) = 258

这些是我的服务器SSL_accept的握手读/写调用。

但是我测试了nginx,openlitespeed,nodejs等。 这些服务器的跟踪日志如下所示;

read(8247, "26311.11*33T2142013052365260r210212232j1778315301312235354e4"..., 1024) = 307
write(8247, "2633=20093316241r76203245e37310rF330l235-32436Y326"..., 1821) = 1821
read(8178, "26311.11*33367._207353316262j27733235223736334330351370X21034x"..., 1024) = 307
write(8178, "2633=200933347322200222206336y2243213<23526523033315375306H57"..., 1821) = 1821
read(8280, "26311.11*3335214374)Vg722536340251*234j35>O2343Dw"..., 1024) = 307
write(8280, "2633=200933240q207T331262314261o3307L{U20c270232377(364"..., 1821) = 1821
read(8567, "26311.11*33255270344736222617276x205305334C16$@Zd2353304"..., 1024) = 307
write(8567, "2633=200933355314fko25C235260213273263o`2234:34427341]"..., 1821) = 1821

对于握手,在我的服务器上部分读取 8 ???

为什么我的SSL_accept功能很慢?

PS. : 对不起,我的语法不好。

编辑;SSL设置部分(SSL设置部分容易复制,但套接字多路复用部分很难复制)

SSL_CTX *ctx = NULL;
void Init_SSL() {
OPENSSL_config(NULL);
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
}
void Init_Server() {
#if OPENSSL_VERSION_NUMBER < 0x10100000L
ctx = SSL_CTX_new(SSLv23_method());
#else
ctx = SSL_CTX_new(TLS_method());
#endif
if (ctx == NULL) {
cout << "can not create ctx" << endl;
return;
}
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3);
SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS);
SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION);
SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
SSL_CTX_set_options(ctx, SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
#endif
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
const unsigned char *TmpStr = "SslSrv";
SSL_CTX_set_session_id_context(ctx, TmpStr, strlen(TmpStr));
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
}
bool LoadCert() {
DH *dh;
BIO *bio = BIO_new_file("server.dh", "r");
if (bio == NULL) {
cout << "dh file error" << endl;
return false;
}
dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
BIO_free(bio);
if (dh == NULL) {
cout << "dh params error" << endl;
return false;
}
const int size = BN_num_bits(dh->p);
if (size < 1024) {
cout << "dh bits size error" << endl;
return false;
}
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
int r = SSL_CTX_set_tmp_dh(ctx, dh);
if (!r) {
cout << "cannot set tmp dh" << endl;
return false;
}
}
if (SSL_CTX_use_certificate_chain_file(ctx, "server.crt") <= 0) {
cout << "cannot load cert" << endl;
return false;
}
if (SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) {
cout << "cannot load priv key file" << endl;
return false;
}
if (!SSL_CTX_check_private_key(ctx)) {
cout << "priv key file is wrong" << endl;
return false;
}
// this list from nodejs (default list)
if (SSL_CTX_set_cipher_list(ctx, "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA") == 0) {
cout << "cannot set cipher list" << endl;
return false;
}
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE);
#if SSL_CTRL_SET_ECDH_AUTO
SSL_CTX_set_ecdh_auto(ctx, 1);
#endif
return true;
}

此设置中是否有任何缺失或无效的内容? 谢谢。

SSL 握手包括关键操作逻辑,包括加密。 我列出了一些处理缓慢的原因:-

  1. 客户端请求访问受保护的资源。
  2. 服务器向客户端提供其证书。
  3. 客户端验证服务器的证书。
  4. 如果成功,客户端会将其证书发送到服务器。
  5. 服务器验证客户端的凭据。
  6. 如果成功,服务器将授予对客户端请求的受保护资源的访问权限

因此,如果您的应用程序中没有 SSL 握手,您的应用程序将避免上述所有操作,显然它会提高操作速度。

速度慢的一个最重要的原因是,如果您考虑上述所有情况,则客户端和服务器之间存在如此多的通信,并且数据解密/加密是在同步中完成的,这也减少了并行处理。