简体   繁体   English

SSL_read 无限期阻止

[英]SSL_read blocks indefinitely

I am trying to read data off an Openssl linked socket using SSL_read.我正在尝试使用 SSL_read 从 Openssl 链接的套接字读取数据。 I perform Openssl operations in client mode that sends command and receives data from a real-world server.我在客户端模式下执行 Openssl 操作,发送命令并从真实世界的服务器接收数据。 I used two threads where one thread handles all Openssl operations like connect, write and close.我使用了两个线程,其中一个线程处理所有 Openssl 操作,如连接、写入和关闭。 I perform the SSL_read in a separate thread.我在单独的线程中执行 SSL_read。 I am able to read data properly when I issue SSL_read once.当我发出 SSL_read 一次时,我能够正确读取数据。

But I ran into problems when I tried to perform multiple connect, write, close sequences.但是当我尝试执行多个连接、写入、关闭序列时遇到了问题。 Ideally I should terminate the thread performing the SSL_read in response to close.理想情况下,我应该终止执行 SSL_read 的线程以响应关闭。 This is because for the next connect we would get a new ssl pointer and so we do not want to perform read on old ssl pointer.这是因为对于下一次连接,我们将获得一个新的 ssl 指针,因此我们不想对旧的 ssl 指针执行读取。 But problem is when I do SSL_read, I am stuck until there is data available in SSL buffer.但问题是,当我执行 SSL_read 时,我会卡住,直到 SSL 缓冲区中有可用数据。 It gets blocked on the SSL pointer, even when I have closed the SSL connection in the other thread.它在 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:
.
.
.
}

I tried all possible solutions to the problem but non works.我尝试了所有可能的解决方案,但没有奏效。 Mostly I tried indication for letting me know there might be data in SSL buffer, but none of it returns proper indication.大多数情况下,我尝试指示让我知道 SSL 缓冲区中可能有数据,但没有一个返回正确的指示。

I tried:我试过:

- Doing SSL_pending first to know if there is data in SSL buffer. - 首先执行 SSL_pending 以了解 SSL 缓冲区中是否有数据。 But this always returns zero但这总是返回零

- Doing select on the Openssl socket to see if it returns value bigger than zero. - 在 Openssl 套接字上执行选择以查看它是否返回大于零的值。 But it always returns zero.但它总是返回零。

- Making the socket as non-blocking and trying the select, but it doesnt seem to work. - 将套接字设为非阻塞并尝试选择,但它似乎不起作用。 I am not sure if I got the code properly.我不确定我是否正确获取了代码。

An example of where I used select for blocking socket is as follows.我使用 select 阻塞套接字的示例如下。 But select always returns zero.但是 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) {
.
.
}

So as you can see I have tried number of ways to get the thread performing SSL_read to terminate in response to close, but I didnt get it to work as I expected.正如您所看到的,我已经尝试了多种方法来让执行 SSL_read 的线程响应关闭而终止,但是我没有让它按预期工作。 Did anybody get to make SSL_read work properly?有人让 SSL_read 正常工作吗? Is non-blocking socket only solution to my problem?非阻塞套接字是否只能解决我的问题? For blocking socket how do you solve the problem of quitting from SSL_read if you never get a response for command?对于阻塞套接字,如果从未收到命令响应,如何解决退出 SSL_read 的问题? Can you give an example of working solution for non blocking socket with read?你能举一个带有读取的非阻塞套接字的工作解决方案的例子吗?

I can point you to a working example of non-blocking client socket with SSL ... https://github.com/darrenjs/openssl_examples我可以为您指出一个使用 SSL 的非阻塞客户端套接字的工作示例... https://github.com/darrenjs/openssl_examples

It uses non-blocking sockets with standard linux IO (based on poll event loop).它使用具有标准 linux IO 的非阻塞套接字(基于轮询事件循环)。 Raw data is read from the socket and then fed into SSL memory BIO's, which then perform the decryption.从套接字读取原始数据,然后将其送入 SSL 内存 BIO,然后执行解密。

The approach I used was single threaded.我使用的方法是单线程的。 A single thread performs the connect, write, and read.单个线程执行连接、写入和读取。 This means there cannot be any problems associated with one thread closing a socket, while another thread is trying to use that socket.这意味着一个线程关闭套接字不会有任何问题,而另一个线程正在尝试使用该套接字。 Also, as noted by the SSL FAQ, "an SSL connection cannot be used concurrently by multiple threads" ( https://www.openssl.org/docs/faq.html#PROG1 ), so single threaded approach avoids problems with concurrent SSL write & read.此外,正如 SSL FAQ 所述,“多个线程不能同时使用 SSL 连接”( https://www.openssl.org/docs/faq.html#PROG1 ),因此单线程方法避免了并发 SSL 的问题写和读。

The challenge with single threaded approach is that you then need to create some kind of synchronized queue & signalling mechanism for submitting and holding data pending for outbound (eg, the commands that you want to send from client to server), and get the socket event loop to detect when there is data pending for write and pull it from the queue etc. For that I would would look at standard std::list, std::mutex etc, and either pipe2 or eventfd for signalling the event loop.单线程方法的挑战是,您需要创建某种同步队列和信号机制来提交和保存等待出站的数据(例如,您想要从客户端发送到服务器的命令),并获取套接字事件循环以检测何时有待写入的数据并将其从队列等中拉出。为此,我会查看标准 std::list、std::mutex 等,以及用于发出事件循环信号的 pipe2 或 eventfd。

OpenSSL calls recv() which in turn obeys the SOCKET's timeout, which by default is infinite. OpenSSL 调用 recv() ,它反过来遵守 SOCKET 的超时,默认情况下是无限的。 You can change the timeout thusly:您可以这样更改超时:

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));
}

Unfortunately, ssl_error_get() returns SSL_ERROR_SYSCALL which it returns in other situations too, so it's not easy to determine that it timed out.不幸的是, ssl_error_get() 返回 SSL_ERROR_SYSCALL ,它在其他情况下也返回,因此不容易确定它超时。 But this function will help you determine if the connection is lost:但是这个函数会帮助你确定连接是否丢失:

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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM