SSL_read无限期阻止

时间:2017-10-01 23:34:12

标签: c sockets ssl openssl

我正在尝试使用SSL_read从Openssl链接套接字读取数据。我在客户端模式下执行Openssl操作,该模式发送命令并从实际服务器接收数据。我使用了两个线程,其中一个线程处理所有Openssl操作,如connect,write和close。我在一个单独的线程中执行SSL_read。当我发出一次SSL_read时,我能够正确读取数据。

但是当我尝试执行多个连接,写入,关闭序列时,我遇到了问题。理想情况下,我应该终止执行SSL_read的线程以响应close。这是因为对于下一次连接,我们将获得一个新的ssl指针,因此我们不希望对旧的ssl指针执行读取。但问题是当我执行SSL_read时,我被困住直到SSL缓冲区中有可用的数据。它在SSL指针上被阻止,即使我在另一个线程中关闭了SSL连接。

while(1) {
    memset(sbuf, 0, sizeof(uint8_t) * TLS_READ_RCVBUF_MAX_LEN);

    read_data_len = SSL_read(con, sbuf, TLS_READ_RCVBUF_MAX_LEN);
    switch (SSL_get_error(con, read)) {
        case SSL_ERROR_NONE:
.
.
.
}

我尝试了所有可能的问题解决方案但不起作用。大多数情况下,我试着让我知道SSL缓冲区中可能有数据,但没有一个返回正确的指示。

我试过了:

- 首先进行SSL_pending以了解SSL缓冲区中是否有数据。但这总是返回零

- 在Openssl套接字上执行select以查看它是否返回大于零的值。但它总是返回零。

- 使套接字成为非阻塞并尝试选择,但它似乎无法正常工作。我不确定我是否正确使用了代码。

我使用select for blocking socket的例子如下。但是select总是返回零。

    while(1) {
    //  The use of Select here is to timeout
    //  while waiting for data to read on SSL. 
    //  The timeout is set to 1 second
    i = select(width, &readfds, NULL,
            NULL, &tv);
    if (i < 0) {
        // Select Error. Take appropriate action for this error
    }

    //  Check if there is data to be read
    if (i > 0) {
        if (FD_ISSET(SSL_get_fd(con), &readfds)) {
            // TODO: We have data in the SSL buffer. But are we
            //       sure that the data is from read buffer? If not,
            //       SSL_read can be stuck indefinitely.
            //       Maybe we can do SSL_read(con, sbuf, 0) followed
            //       by SSL_pending to find out?
            memset(sbuf, 0, sizeof(uint8_t) * TLS_READ_RCVBUF_MAX_LEN);

            read_data_len = SSL_read(con, sbuf, TLS_READ_RCVBUF_MAX_LEN);
            error = SSL_get_error(con, read_data_len);
            switch (error) {
.
.
}

因为你可以看到我已经尝试了多种方法来让线程执行SSL_read以响应close而终止,但是我没有按照我的预期让它工作。有人让SSL_read正常工作吗?非阻塞套接字只能解决我的问题吗?对于阻塞套接字如果从未得到命令响应,如何解决从SSL_read退出的问题?你能给出一个非阻塞套接字工作解决方案的例子吗?

2 个答案:

答案 0 :(得分:0)

我可以向您介绍一个使用SSL的非阻塞客户端套接字的工作示例... https://github.com/darrenjs/openssl_examples

它使用带有标准linux IO的非阻塞套接字(基于轮询事件循环)。从套接字读取原始数据,然后将其馈送到SSL存储器BIO,然后执行解密。

我使用的方法是单线程。单个线程执行连接,写入和读取。这意味着一个线程关闭套接字不会出现任何问题,而另一个线程正在尝试使用该套接字。此外,正如SSL常见问题解答所述,&#34;多个线程不能同时使用SSL连接&#34; (https://www.openssl.org/docs/faq.html#PROG1),因此单线程方法避免了并发SSL写入和问题的问题。读。

单线程方法面临的挑战是,您需要创建某种同步队列&amp;用于提交和保持出站数据的信令机制(例如,您希望从客户端发送到服务器的命令),并获取套接字事件循环以检测何时有待写入的数据并从队列中提取等等。我会看一下标准的std :: list,std :: mutex等,以及pipe2或eventfd来发信号通知事件循环。

答案 1 :(得分:0)

OpenSSL调用recv(),它依次服从SOCKET的超时,默认情况下是无限的。您可以这样更改超时时间:

void socket_timeout_receive_set(SOCKET handle, dword milliseconds)
{
if(handle==SOCKET_HANDLE_NULL)
    return;

struct timeval tv = { long(milliseconds / 1000), (milliseconds % 1000) * 1000 };
setsockopt(handle, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
}

不幸的是,ssl_error_get()返回SSL_ERROR_SYSCALL,在其他情况下也会返回SSL_ERROR_SYSCALL,因此要确定它是否超时并不容易。但是此功能将帮助您确定连接是否丢失:

bool socket_dropped(SOCKET handle)
{
// Special thanks:  "Detecting and terminating aborted TCP/IP connections" by Vinayak Gadkari

if(handle==SOCKET_HANDLE_NULL)
    return true;

// create a socket set containing just this socket
fd_set socket_set;
FD_ZERO(&socket_set);
FD_SET(handle, &socket_set);

// if the connection is unreadable, it is not dropped (strange but true)
static struct timeval timeout = { 0, 0 };
int count = select(0, &socket_set, NULL, NULL, &timeout);
if(count <= 0) {
    // problem: count==0 on a connection that was cut off ungracefully, presumably by a busy router
    // for connections that are open for a long time but may not talk much, call keepalive_set()
    return false;
    }

if(!FD_ISSET(handle, &socket_set))  // creates a dependency on __WSAFDIsSet()
    return false;

// peek at the next character
// recv() returns 0 if the connection was dropped
char dummy;
count = recv(handle, &dummy, 1, MSG_PEEK);

if(count > 0)
    return false;

if(count==0)
    return true;

return sec==WSAECONNRESET || sec==WSAECONNABORTED || sec==WSAENETRESET || sec==WSAEINVAL;
}