SSL_accept with edge triggered non-blocking epoll always returns SSL_ERROR_WANT_READ
<p>There are 3 of us working on adding SSL to epoll based server but after 3 days of pull-your-hair out frustration we decided to ask the world for help.</p>

<p>The problem is that <code>SSL_accept()</code> always returns <code>SSL_ERROR_WANT_READ</code>, we DO have epoll events for EPOLLIN and EPOLLOUT for the associated socket but even when we get an event and recall the <code>SSL_accept</code> it just returns the want read....  so frustrating.  </p>

<p>There seems to be no good working example of using openssl with epoll that we can find, PLEASE send us to code samples if there are any, we have spent days on the this problem and read about every SE/SO message and spent hours on the openssl mailing list. </p>

<p>We have a basic app structure that looks like this for the accept:</p>

<pre><code>            if (http_sock == source_fd || https_sock == source_fd) {
                // new connection
                req_type_t req_type = (http_sock == source_fd) ? HTTP : HTTPS;
                int infd;

                while (1) {
                    struct sockaddr_in in_addr;
                    socklen_t in_addr_len;
                    ZLOG_DBG(zlog_cat, "New %s request on source_fd: %d http_sock: %d https_sock: %d", req_type == HTTP ? "HTTP" : "HTTPS", source_fd, http_sock, https_sock);
                    in_addr_len = sizeof in_addr;
                    infd = accept4(source_fd, &amp;in_addr, &amp;in_addr_len, SOCK_NONBLOCK);
                    if (infd &lt;= 0)
                    {
                        if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
                            /* We have processed all incoming
                             connections. */
                             break;
                        } else {
                          ZLOG_ERR(zlog_cat, "ERROR: accept");
                          break;
                        }
                    }

                    req = new_req_data(kds, req_type, &amp;g_data, infd, &amp;in_addr, in_addr_len, cert_q);


                    event.events = EPOLLIN | EPOLLRDHUP | EPOLLET;

                    if (req-&gt;type == HTTPS) {
                        req-&gt;c_tls.want_ssl_accept = 1;
                        event.events = EPOLLIN | EPOLLRDHUP | EPOLLOUT | EPOLLET;
                        ssl_establish(req);
                    }
                    if ( (s = set_socket_blocking(req-&gt;client.fd, 1)) &lt; 0)
                        err(EXIT_FAILURE, "[%s:%d] set_socket_blocking()", __FUNCTION__, __LINE__);

                    epoll_event_t *epoll_event = new_epoll_event_t(infd, req);

                    event.data.ptr = epoll_event;
                    // add new infd to our epoll listener
                    if ( (s = epoll_ctl(efd, EPOLL_CTL_ADD, req-&gt;client.fd, &amp;event)) == -1 )
                        err(EXIT_FAILURE, "epoll_ctl(http) %s", strerror(errno));
                }
                ZLOG_DBG(zlog_cat, "New %s connection from %s:%d on FD %d mac_address: %s", req-&gt;type ? "HTTPS" : "HTTP",
                    inet_ntoa(req-&gt;client.sock.sin_addr), ntohs(req-&gt;client.sock.sin_port), req-&gt;client.fd, req-&gt;device-&gt;mac_address);
                g_data.conn_counter++;
                continue; // continue to next accept event

            } // end new connection
</code></pre>

<p>The <code>ssl_establish()</code> method looks like this:</p>

<pre><code>int ssl_establish(req_data_t *req) {
unsigned long e;
int ssl_err, rc;

// create openssl server context and ssl object
if ((req-&gt;s_tls.ctx = create_context(0)) == NULL) {
    ZLOG_ERR(zlog_cat, "create_context() %s", strerror(errno));
    return -1;
}

if ((req-&gt;s_tls.ssl = SSL_new(req-&gt;s_tls.ctx)) == NULL) {
    ZLOG_ERR(zlog_cat, "SSL_new() %s", strerror(errno));
    return -1;
}

// Establishing SSL/TLS connections with client
if (req-&gt;https_def_rsa != NULL &amp;&amp; req-&gt;https_def_cert != NULL) {
    req-&gt;c_tls.servername = req-&gt;https_def_domain;
    req-&gt;c_tls.cert = req-&gt;https_def_cert;
    req-&gt;c_tls.rsa_key = req-&gt;https_def_rsa;
    req-&gt;c_tls.dest_ip = req-&gt;orig.sock.sin_addr.s_addr;
    req-&gt;c_tls.dc_cache = req-&gt;dc_cache;
    req-&gt;c_tls.q_entry = NULL;

    if (req-&gt;c_tls.ctx == NULL ) {
        if ((req-&gt;c_tls.ctx = create_context(1)) == NULL)
            ZLOG_ERR(zlog_cat, "ERROR create_context");
        if (configure_context(&amp;req-&gt;c_tls))
            ZLOG_ERR(zlog_cat, "ERROR configure_context");
        if ((req-&gt;c_tls.ssl = SSL_new(req-&gt;c_tls.ctx)) == NULL)
            ZLOG_ERR(zlog_cat, "ERROR ssl_new");

        // SSL_set_mode(req-&gt;c_tls.ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);  // This doesn't seem to do anything

        if (SSL_set_fd(req-&gt;c_tls.ssl, req-&gt;client.fd) == 0)
            ZLOG_ERR(zlog_cat, "ERROR ssl_set_fd");
    }
    req-&gt;client.ssl = req-&gt;c_tls.ssl;
}
if ((rc = SSL_accept(req-&gt;c_tls.ssl)) &lt; 0) {
    ssl_err = SSL_get_error(req-&gt;c_tls.ssl, rc);

    switch (ssl_err) {
        case SSL_ERROR_WANT_READ:
            ZLOG_DBG(zlog_cat, "SSL_accept SSL_ERROR_WANT_READ");
            req-&gt;c_tls.want_ssl_accept = 1;
            return 0;
            break;
        case SSL_ERROR_WANT_WRITE:
            ZLOG_DBG(zlog_cat, "SSL_accept SSL_ERROR_WANT_WRITE");
            req-&gt;c_tls.want_ssl_accept = 1;
            return 0;
            break;
        case SSL_ERROR_ZERO_RETURN:
            ZLOG_ERR(zlog_cat, "SSL_accept SSL_ERROR_ZERO_RETURN");
            req-&gt;c_tls.want_ssl_accept = 0;
            return -1;
            break;
        case SSL_ERROR_WANT_X509_LOOKUP:
            ZLOG_ERR(zlog_cat, "SSL_accept SSL_ERROR_WANT_X509_LOOKUP");
            req-&gt;c_tls.want_ssl_accept = 0;
            return -1;
            break;
        case SSL_ERROR_SYSCALL:
            ZLOG_ERR(zlog_cat, "SSL_accept SSL_ERROR_SYSCALL");
            req-&gt;c_tls.want_ssl_accept = 0;
            return -1;
            break;
        case SSL_ERROR_SSL:
            ZLOG_ERR(zlog_cat, "SSL_accept SSL_ERROR_SSL");
            req-&gt;c_tls.want_ssl_accept = 0;
            return -1;
            break;
    }
}
ZLOG_DBG(zlog_cat, "SSL_accept success");
req-&gt;c_tls.want_ssl_accept = 0;
return 1;
}
</code></pre>

<p>In the pollin / pollout handlers we do this:</p>

<pre><code>                if (req-&gt;type == HTTPS &amp;&amp; req-&gt;c_tls.want_ssl_accept) {
                    ZLOG_DBG(zlog_cat, "ssl_establish EPOLLIN");
                    if ((rc = ssl_establish(req)) &lt;= 0) {
                        ZLOG_DBG(zlog_cat, "ssl_establish EPOLLIN retry...");
                        continue;
                    }
                }
</code></pre>

<p>Our log looks like this:</p>

<pre><code>2016-08-03 23:26:32.224  New HTTPS connection from 192.168.1.195:52659 on FD 15 mac_address: e4:f8:9c:85:63:99
2016-08-03 23:26:32.224  epoll_wait returned 1
2016-08-03 23:26:32.224  epoll event number: 0 on fd: 15
2016-08-03 23:26:32.224  Reading data from Client, source_fd: 15 source_sock-&gt;fd: 15 dest_sock-&gt;fd: -1 mac_address: e4:f8:9c:85:63:99
2016-08-03 23:26:32.224  ssl_establish EPOLLIN
2016-08-03 23:26:32.484  SSL_accept SSL_ERROR_WANT_READ
2016-08-03 23:26:32.484  ssl_establish EPOLLIN retry...
2016-08-03 23:26:32.954  New HTTPS connection from 192.168.1.195:52660 on FD 16 mac_address: e4:f8:9c:85:63:99
2016-08-03 23:26:32.954  epoll_wait returned 1
2016-08-03 23:26:32.954  epoll event number: 0 on fd: 16
2016-08-03 23:26:32.954  Establishing SSL (EPOLLOUT)
2016-08-03 23:26:32.974  SSL_accept SSL_ERROR_WANT_READ
2016-08-03 23:26:32.974  ssl_establish EPOLLOUT retry...
</code></pre>