问题:
我看到间歇性问题导致SSL连接被丢弃。我能够在我的测试环境中重现这个问题(50k调用中有1个失败)。我正在测试的软件是SMTP / POP3 / IMAP服务器。
在SMTP客户端和服务器之间来回发送几条命令后,成功执行握手后,SSL连接将被删除。
我的服务器软件(在Windows上运行)使用以下方式编写:
客户端应用程序使用.NET3.5,SslStream
编写该应用程序与HTTP Server 3 in the Boost examples
非常相似我正在制作的电话如下:
boost::asio::async_read_until(ssl_socket_, receive_buffer_, delimitor, strand_.wrap(AsyncReadCompletedFunction));
在AsyncReadCompletedFunction中,错误包含以下内容:
有时错误是:
或者:
这些错误由OpenSSL设置。
隔离
我已经确保所有对Asio的调用都经过了一次。对async_write / async_read的调用和完成处理程序都在一个strand中执行,如下所示。
std::shared_ptr<TCPConnection> shared_this = shared_from_this();
strand_.post([AsyncWriteCompletedFunction, send_buffer_, shared_this]
{
boost::asio::async_write (shared_this->ssl_socket_, send_buffer_, shared_this->strand_.wrap(AsyncWriteCompletedFunction));
});
我测试了多个变体,例如只是将处理程序包装在一个链中,而只是在一个链中调用boost :: asio :: async_write。
我已经在我的方法中引入了同步日志记录文件,并确认没有重叠的读/写调用:
2014-12-01 13:12:45.362: Session 2254, Starting AsyncWrite - Call to boost::asio::async_write
2014-12-01 13:12:45.362: Session 2254, AsyncWriteCompleted - Write completion handler called
2014-12-01 13:12:45.362: Session 2254, Starting AsyncRead - Call to boost::asio::async_read
2014-12-01 13:12:45.362: Session 2254, AsyncReadCompleted - Read completion handler called, with error:
The read operation failed. Bytes transferred: 0 Remote IP: 127.0.0.1, Session: 2254, Code: 67567722, Message: block type is not 01
我认为这可能是我用来运行测试的客户端中的错误。但它是单线程的,并使用具有同步调用的循环实现。我已经仔细检查了.NET Framework 3.5中SslStream类的文档,但是也看不到任何错误(客户端代码非常简单)。
测试在Windows XP Service Pack 3上运行。我正在其他操作系统上运行测试:es,但到目前为止,我没有看到那里。
编辑1:
我已经安装了Wireshark来跟踪SMTP流量。我看到以下内容:
查看服务器日志时,我可以看到服务器从未收到过步骤7中发送的客户端用户名。 async_read操作已完成,并且调用了完成处理程序,错误代码为336265225:PEM lib“
我已经没有想法进行测试了。有人建议应该尝试什么?
答案 0 :(得分:1)
此问题是由Asio SSL实施中的错误引起的。向Asio Github issue tracker报告了它。
当OpenSSL发生错误时,会将错误代码推送到队列中。单次调用OpenSSL可能会导致多个错误代码被推送到队列。例如,OpenSSL中的低级组件可能会将一个错误推送到队列,而另一个更高级别的组件可能会推送另一个错误。 (它可以被视为一堆错误)。
这些队列链接到该线程。在Windows的Asio imlplementation中,这是使用thread-id(int)完成的。所以每个线程都有自己的包含错误的队列。
Asio执行SSL功能后,只会从队列中删除第一个错误。如果已将多个项目推送到队列,则剩余的项目将被留下。这样做的结果是,当Asio稍后调用SSL函数并检查队列时,它可能会从上一个操作中获取错误,并认为当前操作已出现故障,即使它没有。
有时当Asio调用OpenSSL时,它会先清除队列。例如,函数add_certificate_authority通过执行:: ERR_clear_error()开始。似乎其他人有同样的问题并声称这是正确的解决方案。但是Asio中的其他功能,例如async_read不会这样做。
运行我的测试套件时发现了我的问题。测试套件包含许多测试错误处理的测试,例如通过向SSL服务器发送无效数据,尝试使用密码无效的私钥等等,以及这里开始的乐趣: / p>
我的测试套件会触发代码:
context.set_password_callback(callbackfunc)
context.use_private_key_file(myfile, boost::asio::ssl::context::pem)
回调函数将返回错误的密码(以测试是否正确处理)。 use_private_key_file将调用SSL_CTX_use_PrivateKey_file,将两个错误推送到队列。但asios实现use_private_key_file只删除第一项。 use_private_key_file正确地向我的应用程序代码报告错误。
我的应用程序代码检测到这一点,并触发重新启动服务组件(清理,重新启动线程等)。实际的Windows进程将继续运行。
一段时间后,当我调用async_read时,该调用可能恰好在一个线程上执行,该线程与我在一小时前调用use_private_key_file的线程具有相同的ID。它是真正不同的线程(原始线程终止),但它们碰巧具有相同的Thread-Id。在Asio调用SSL_read之后,它将检查错误队列中当前线程id并检索先前在use_private_key_file调用中报告的错误。
这就是async_read如何成功但仍然失败,错误指的是完全不同的东西。