引擎ssl 收包简述

  目前引擎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
复制代码
posted @   codestacklinuxer  阅读(286)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示