目前引擎ssl逻辑在ssl_read ssl_shuwdown 上存在问题!
主要问题是: 错误处理不正确,很多错误遗漏,基本只有正常逻辑
首先看下 ssl_read 官方文档解释
In the following, SSL_read() and SSL_peek() are called “read functions”. If necessary, a read function will negotiate a TLS session, if not already explicitly performed by SSL_connect(3) or SSL_accept(3). If the peer requests a re-negotiation,
it will be performed transparently during the read function operation. The behaviour of the read functions depends on the underlying BIO. For the transparent negotiation to succeed, the ssl must have been initialized to client or server mode. This is done by calling SSL_set_connect_state(3) or SSL_set_accept_state(3)
before the first call to a read function. The read functions works based on the TLS records. The data are received in records (with a maximum record size of 16kB). Only when a record has been completely received,
it can be processed (decrypted and checked for integrity). Therefore data that was not retrieved at the last read call can still be buffered inside the TLS layer and will be retrieved on
the next read call. If num is higher than the number of bytes buffered, the read functions will return with the bytes buffered.
If no more bytes are in the buffer, the read functions will trigger the processing of the next record. Only when the record has been received and processed
completely will the read functions return reporting success. At most the contents of the record will be returned.
As the size of a TLS record may exceed the maximum packet size of the underlying transport (e.g., TCP), it may be necessary to read several packets from the transport layer before the record
is complete and the read call can succeed. If the underlying BIO is blocking, a read function will only return once the read operation has been finished or an error occurred, except when a renegotiation takes place,
in which case an SSL_ERROR_WANT_READ may occur. This behavior can be controlled with the SSL_MODE_AUTO_RETRY flag of the SSL_CTX_set_mode(3) call. If the underlying BIO is non-blocking, a read function will also return when the underlying BIO could not satisfy the needs of the function to continue the operation.
In this case a call to SSL_get_error(3) with the return value of the read function will yield SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE. As at any time a re-negotiation is possible,
a read function may also cause write operations. The calling process must then repeat the call after taking appropriate action to satisfy the needs of the read function.
The action depends on the underlying BIO. When using a non-blocking socket, nothing is to be done, but select(2) can be used to check for the required condition.
When using a buffering BIO, like a BIO pair, data must be written into or retrieved out of the BIO before being able to continue. SSL_pending(3) can be used to find out whether there are buffered bytes available for immediate retrieval. In this case a read function can be called without blocking
or actually receiving new data from the underlying socket. When a read function operation has to be repeated because of SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, it must be repeated with the same arguments. RETURN VALUES The following return values can occur: >0 The read operation was successful. The return value is the number of bytes actually read from the TLS connection. 0 The read operation was not successful. The reason may either be a clean shutdown due to a “close notify” alert sent by the peer (in which case the SSL_RECEIVED_SHUTDOWN flag
in the ssl shutdown state is set (see SSL_shutdown(3) andSSL_set_shutdown(3)). It is also possible that the peer simply shut down the underlying transport and the shutdown is incomplete.
Call SSL_get_error(3) with the return value to find out whether an error occurred or the connection was shut down cleanly (SSL_ERROR_ZERO_RETURN). <0 The read operation was not successful, because either an error occurred or action must be taken by the calling process. Call SSL_get_error(3) with the return value to find out the reason.
SSL_read是工作在SSL/TLS的记录之上的。数据按照记录来接收的(最大记住是16KB SSLv3/TLS)。只有在一个记录被完整接收之后才会被处理(解密和验证)。因此SSL_read只会在记录数据都读取成功了才能返回数据,否则SSL_read只会触发读取下一个记录组。如果num的数量比缓冲的数据量大,那么SSL_read会返回缓冲区的内容;如果缓冲区没有内容,那么触发读取下个记录。SSL_read最多返回的就是一个记录的长度。由于SSL/TLS记录的大小可能超过底层TCP包的大小,所以有可能需要让SSL读取多个TCP包,SSL_read才能成功。
ssl_read异常场景为: 返回值<=0实,其ssl_errno=SSL_get_error()值为部分特殊值时:
SSL_ERROR_SYSCALL
SSL_ERROR_WANT_READ
SSL_ERROR_WANT_WRITE
SSL_ERROR_ZERO_RETURN
有时考虑太多反而是为了规避一些初始就有的问题!
ssl_shuwdown :
来看下 ngx 是怎么处理ssl 关闭的
ngx_int_t ngx_ssl_shutdown(ngx_connection_t *c) { int n, sslerr, mode; ngx_err_t err; if (c->timedout) { mode = SSL_RECEIVED_SHUTDOWN|SSL_SENT_SHUTDOWN; SSL_set_quiet_shutdown(c->ssl->connection, 1);// 关闭的时候不告诉对端 } else { mode = SSL_get_shutdown(c->ssl->connection); if (c->ssl->no_wait_shutdown) { mode |= SSL_RECEIVED_SHUTDOWN; } if (c->ssl->no_send_shutdown) { mode |= SSL_SENT_SHUTDOWN; } if (c->ssl->no_wait_shutdown && c->ssl->no_send_shutdown) { SSL_set_quiet_shutdown(c->ssl->connection, 1); } } SSL_set_shutdown(c->ssl->connection, mode); ngx_ssl_clear_error(c->log); n = SSL_shutdown(c->ssl->connection); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_shutdown: %d", n); sslerr = 0; /* SSL_shutdown() never returns -1, on error it returns 0 */ if (n != 1 && ERR_peek_error()) { sslerr = SSL_get_error(c->ssl->connection, n); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); } if (n == 1 || sslerr == 0 || sslerr == SSL_ERROR_ZERO_RETURN) { SSL_free(c->ssl->connection); c->ssl = NULL; return NGX_OK; } if (sslerr == SSL_ERROR_WANT_READ || sslerr == SSL_ERROR_WANT_WRITE) { c->read->handler = ngx_ssl_shutdown_handler; c->write->handler = ngx_ssl_shutdown_handler; if (ngx_handle_read_event(c->read, 0, NGX_FUNC_LINE) != NGX_OK) { return NGX_ERROR; } if (ngx_handle_write_event(c->write, 0, NGX_FUNC_LINE) != NGX_OK) { return NGX_ERROR; } if (sslerr == SSL_ERROR_WANT_READ) { ngx_add_timer(c->read, 30000, NGX_FUNC_LINE); } return NGX_AGAIN; } err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; ngx_ssl_connection_error(c, sslerr, err, "SSL_shutdown() failed"); SSL_free(c->ssl->connection); c->ssl = NULL; return NGX_ERROR; }
其核心逻辑就是:
- SSL_shutdown
- SSL_get_error
对于 SSL_set_quiet_shutdown 以及 mode 不是很清楚,目前发现 直接调用ssl_shutdown 也没有出现问题,对于ngx 为啥关闭前 会维护 这个关闭mode,暂时不想了解其业务为啥有这个需求
其具体介绍可以见:https://www.openssl.org/docs/man1.0.2/man3/SSL_shutdown.html
0 The shutdown is not yet finished. Call SSL_shutdown() for a second time, if a bidirectional shutdown shall be performed. The output of SSL_get_error(3) may be misleading,
as an erroneous SSL_ERROR_SYSCALL may be flagged even though no error occurred. 1 The shutdown was successfully completed. The "close notify" alert was sent and the peer's "close notify" alert was received. <0 The shutdown was not successful because a fatal error occurred either at the protocol level or a connection failure occurred. It can also occur if action is
need to continue the operation for non-blocking BIOs. Call SSL_get_error(3) with the return value ret to find out the reason.
ngx_int_t ngx_ssl_shutdown(ngx_connection_t *c) { int n, sslerr, mode; ngx_err_t err; if (c->timedout) { mode = SSL_RECEIVED_SHUTDOWN|SSL_SENT_SHUTDOWN; SSL_set_quiet_shutdown(c->ssl->connection, 1); } else { mode = SSL_get_shutdown(c->ssl->connection); if (c->ssl->no_wait_shutdown) { mode |= SSL_RECEIVED_SHUTDOWN; } if (c->ssl->no_send_shutdown) { mode |= SSL_SENT_SHUTDOWN; } if (c->ssl->no_wait_shutdown && c->ssl->no_send_shutdown) { SSL_set_quiet_shutdown(c->ssl->connection, 1); } } SSL_set_shutdown(c->ssl->connection, mode); ngx_ssl_clear_error(c->log); n = SSL_shutdown(c->ssl->connection); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_shutdown: %d", n); sslerr = 0; /* SSL_shutdown() never returns -1, on error it returns 0 */ if (n != 1 && ERR_peek_error()) { sslerr = SSL_get_error(c->ssl->connection, n); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); } if (n == 1 || sslerr == 0 || sslerr == SSL_ERROR_ZERO_RETURN) { SSL_free(c->ssl->connection); c->ssl = NULL; return NGX_OK; } if (sslerr == SSL_ERROR_WANT_READ || sslerr == SSL_ERROR_WANT_WRITE) { c->read->handler = ngx_ssl_shutdown_handler; c->write->handler = ngx_ssl_shutdown_handler; if (ngx_handle_read_event(c->read, 0, NGX_FUNC_LINE) != NGX_OK) { return NGX_ERROR; } if (ngx_handle_write_event(c->write, 0, NGX_FUNC_LINE) != NGX_OK) { return NGX_ERROR; } if (sslerr == SSL_ERROR_WANT_READ) { ngx_add_timer(c->read, 30000, NGX_FUNC_LINE); } return NGX_AGAIN; } err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; ngx_ssl_connection_error(c, sslerr, err, "SSL_shutdown() failed"); SSL_free(c->ssl->connection); c->ssl = NULL; return NGX_ERR