Netty tcnative 的 OpenSSL SSL_connect SYSCALL 错误
OpenSSL SSL_connect SYSCALL error with Netty tcnative
我正在使用 C++ 在 Windows 上使用 Berkeley 套接字和 OpenSSL 1.1.1k 连接到 Java (netty-tcnative) 服务器。有时在连接阶段,我会收到来自 SSL_connect
的 SSL_ERROR_SYSCALL
。如果我尝试使用 ERR_get_error
从这个 SYSCALL 错误中获取更多信息(根据 OpenSSL 文档),我会得到一个 return 值 0。TLS 连接的状态是 SSLv3/TLS write client hello
。经过我这边的一些测试,我发现问题只在我重新连接套接字时重现(可能是 TLS 会话恢复?)
这里有 3 个问题:
- 我是否可以设置一个标志或布尔值来启用 OpenSSL 端的调试日志记录,或者是否有一种方法可以让我验证我对 OpenSSL 试图恢复 TLS 会话的怀疑?
- 是否有一个标志让我在 Netty tcnative 端设置以记录类似于
-Djavax.net.debug=all
的 TLS 握手?
- 这是客户端问题还是服务器端问题?
我在 Debian 10 上使用 2.0.40 版本的 netty-tcnative。
我的 C++ 客户端代码是 运行 on MSVC 2017 和 Windows 10.
用于创建 SSL_ctx
:
的 C++ 代码
auto ssl_ctx = SSL_CTX_new(TLS_client_method());
SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION); // Use TLS 1.3 if possible
SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION); // Use TLS 1.2 at a minimum
客户端连接的C++代码:
const int sslErr = SSL_connect(ssl);
if (sslErr <= 0) {
const int sslErrCode = SSL_get_error(ssl, sslErr);
if (sslErrorCode == SSL_ERROR_SYSCALL) {
const unsigned long errGetErr = ERR_get_error();
//const OSSL_HANDSHAKE_STATE state = SSL_get_state(ssl);
const char* stateStr = SSL_state_string_long(ssl);
std::cout << "SSL_connect error: " << sslErrCode << std::endl;
std::cout << "ERR_get_error error: " << errGetErr << std::endl;
std::cout << "state: " << stateStr << std::endl;
if (sslErrCode == SSL_ERROR_SYSCALL) {
char buffer[256] = { 0 };
ERR_error_string_n(errGetErr, buffer, sizeof(buffer));
printf_s(buffer);
printf_s("\n");
}
}
}
程序输出:
SSL_connect error: 5
ERR_get_error error: 0
state: SSLv3/TLS write client hello
error:00000000:lib(0):func(0):reason(0)
服务器端代码(netty 管道):
@Override
protected final void initChannel(Channel ch) throws Exception {
ChannelPipeline pipe = ch.pipeline();
SslContext sslCtx = SslContextBuilder.forServer(keyCertChainFile, keyFile).sslProvider(SslProvider.OPENSSL).build();
pipe.addLast("ssl", sslCtx.newHandler(ch.alloc()));
}
经过大量调试,我终于找到了这个问题的答案。
- 我是否可以设置一个标志或布尔值来启用 OpenSSL 端的调试日志记录,或者是否有一种方法可以让我验证我对 OpenSSL 试图恢复 TLS 会话的怀疑?
如果您使用的是 BoringSSL 而不是 Netty-tcnative OpenSSL 动态库,则 不 支持 TLS 重新协商。事实上,BoringSSL 根本不支持 TLS 重新协商。他们的文档中提到了 here
- 是否有一个标志让我在 Netty tcnative 端设置以记录类似于 -Djavax.net.debug=all 的 TLS 握手?
对于阅读本文的 reader,如果您碰巧 运行 遇到这个问题,我强烈建议您确保在服务器处理程序中记录所有 TLS 握手错误。请参考以下代码段,因为这会在发生握手错误时提醒您。
@Override
public final void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof SslHandshakeCompletionEvent) {
SslHandshakeCompletionEvent hsEvt = (SslHandshakeCompletionEvent) evt;
if (hsEvt.isSuccess()) {
// handshake success
} else {
Throwable cause = hsEvt.cause();
LOGGER.error("SSL Handshake Failure: {}", cause.getLocalizedMessage(), cause);
}
}
}
- 这是客户端问题还是服务器端问题?
就我个人而言,这是一个客户端问题。在深入了解版本 1.1.1l 的 OpenSSL 文档后,有一个快速的 side-note 说明当 SSL_ERROR_SYSCALL
出现时错误号为 0(使用 ERR_get_error
获取)表示 EOF 来自同行。从这个注释中,您应该看到谁在关闭连接并检查套接字的状态。因为我使用的是 winsock,所以我得到的错误是 WSAENOTCONN
,这意味着当我尝试 SSL_connect
时连接无效。 SSL_connect
在引擎盖下执行套接字 writes/reads ,因为它在这里执行 TLS 握手。如果在套接字未连接时发生任何套接字writes/reads,则WSAGetLastError
将returnWSAENOTCONN
。我没有看到任何表明 Netty 端连接关闭的日志,所以我决定使用 Wireshark 深入研究这个问题,以确定 TLS 握手在路径中的哪个位置失败,以及连接是在客户端还是服务器端关闭。在翻阅了一些在线文档后,我发现如果 SSL_read
或 SSL_write
在 OpenSSL 中失败,则 SSL_ERROR_SYSCALL
是一个通用错误代码 returned。用Wireshark分析后,发现客户端一直没有给服务器发送的SYN-ACK发送ACK,说明连接一直没有完全建立。因为我使用的是非阻塞套接字,所以我只需要确保在尝试 TLS 握手之前套接字已完成连接。
希望这对遇到类似问题的人有所帮助。
我正在使用 C++ 在 Windows 上使用 Berkeley 套接字和 OpenSSL 1.1.1k 连接到 Java (netty-tcnative) 服务器。有时在连接阶段,我会收到来自 SSL_connect
的 SSL_ERROR_SYSCALL
。如果我尝试使用 ERR_get_error
从这个 SYSCALL 错误中获取更多信息(根据 OpenSSL 文档),我会得到一个 return 值 0。TLS 连接的状态是 SSLv3/TLS write client hello
。经过我这边的一些测试,我发现问题只在我重新连接套接字时重现(可能是 TLS 会话恢复?)
这里有 3 个问题:
- 我是否可以设置一个标志或布尔值来启用 OpenSSL 端的调试日志记录,或者是否有一种方法可以让我验证我对 OpenSSL 试图恢复 TLS 会话的怀疑?
- 是否有一个标志让我在 Netty tcnative 端设置以记录类似于
-Djavax.net.debug=all
的 TLS 握手? - 这是客户端问题还是服务器端问题?
我在 Debian 10 上使用 2.0.40 版本的 netty-tcnative。
我的 C++ 客户端代码是 运行 on MSVC 2017 和 Windows 10.
用于创建 SSL_ctx
:
auto ssl_ctx = SSL_CTX_new(TLS_client_method());
SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION); // Use TLS 1.3 if possible
SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION); // Use TLS 1.2 at a minimum
客户端连接的C++代码:
const int sslErr = SSL_connect(ssl);
if (sslErr <= 0) {
const int sslErrCode = SSL_get_error(ssl, sslErr);
if (sslErrorCode == SSL_ERROR_SYSCALL) {
const unsigned long errGetErr = ERR_get_error();
//const OSSL_HANDSHAKE_STATE state = SSL_get_state(ssl);
const char* stateStr = SSL_state_string_long(ssl);
std::cout << "SSL_connect error: " << sslErrCode << std::endl;
std::cout << "ERR_get_error error: " << errGetErr << std::endl;
std::cout << "state: " << stateStr << std::endl;
if (sslErrCode == SSL_ERROR_SYSCALL) {
char buffer[256] = { 0 };
ERR_error_string_n(errGetErr, buffer, sizeof(buffer));
printf_s(buffer);
printf_s("\n");
}
}
}
程序输出:
SSL_connect error: 5
ERR_get_error error: 0
state: SSLv3/TLS write client hello
error:00000000:lib(0):func(0):reason(0)
服务器端代码(netty 管道):
@Override
protected final void initChannel(Channel ch) throws Exception {
ChannelPipeline pipe = ch.pipeline();
SslContext sslCtx = SslContextBuilder.forServer(keyCertChainFile, keyFile).sslProvider(SslProvider.OPENSSL).build();
pipe.addLast("ssl", sslCtx.newHandler(ch.alloc()));
}
经过大量调试,我终于找到了这个问题的答案。
- 我是否可以设置一个标志或布尔值来启用 OpenSSL 端的调试日志记录,或者是否有一种方法可以让我验证我对 OpenSSL 试图恢复 TLS 会话的怀疑?
如果您使用的是 BoringSSL 而不是 Netty-tcnative OpenSSL 动态库,则 不 支持 TLS 重新协商。事实上,BoringSSL 根本不支持 TLS 重新协商。他们的文档中提到了 here
- 是否有一个标志让我在 Netty tcnative 端设置以记录类似于 -Djavax.net.debug=all 的 TLS 握手?
对于阅读本文的 reader,如果您碰巧 运行 遇到这个问题,我强烈建议您确保在服务器处理程序中记录所有 TLS 握手错误。请参考以下代码段,因为这会在发生握手错误时提醒您。
@Override
public final void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof SslHandshakeCompletionEvent) {
SslHandshakeCompletionEvent hsEvt = (SslHandshakeCompletionEvent) evt;
if (hsEvt.isSuccess()) {
// handshake success
} else {
Throwable cause = hsEvt.cause();
LOGGER.error("SSL Handshake Failure: {}", cause.getLocalizedMessage(), cause);
}
}
}
- 这是客户端问题还是服务器端问题?
就我个人而言,这是一个客户端问题。在深入了解版本 1.1.1l 的 OpenSSL 文档后,有一个快速的 side-note 说明当 SSL_ERROR_SYSCALL
出现时错误号为 0(使用 ERR_get_error
获取)表示 EOF 来自同行。从这个注释中,您应该看到谁在关闭连接并检查套接字的状态。因为我使用的是 winsock,所以我得到的错误是 WSAENOTCONN
,这意味着当我尝试 SSL_connect
时连接无效。 SSL_connect
在引擎盖下执行套接字 writes/reads ,因为它在这里执行 TLS 握手。如果在套接字未连接时发生任何套接字writes/reads,则WSAGetLastError
将returnWSAENOTCONN
。我没有看到任何表明 Netty 端连接关闭的日志,所以我决定使用 Wireshark 深入研究这个问题,以确定 TLS 握手在路径中的哪个位置失败,以及连接是在客户端还是服务器端关闭。在翻阅了一些在线文档后,我发现如果 SSL_read
或 SSL_write
在 OpenSSL 中失败,则 SSL_ERROR_SYSCALL
是一个通用错误代码 returned。用Wireshark分析后,发现客户端一直没有给服务器发送的SYN-ACK发送ACK,说明连接一直没有完全建立。因为我使用的是非阻塞套接字,所以我只需要确保在尝试 TLS 握手之前套接字已完成连接。
希望这对遇到类似问题的人有所帮助。