OpenSSL状态机中可选消息的处理

简介:
openssl在实现ssl握手的时候是用状态机实现的,实际上linux内核的tcp协议也是状态机实现的,原因就在于这些协议本身的握手规则就是状态机(简直是废话)。SSLv3握手规则要比tcp复杂的多,因此它的实现如果要既美观又高效的话就一定需要很多技巧,造成这种结果的原因就在于ssl握手过程中存在很多的可选消息,而这些可选消息是否存在并不简单依赖与前一个或者前若干个消息的消息头,而只有解析了前面的消息体内容之后才能知道是否存在可选消息,在这个意义上可以说客户端的握手状态机实现要么保存曾经的消息遗留的状态,要么不保留状态,默认按状态机转化顺序处理任何消息(还好,状态机有且只有一种顺序),如果采用前一个方案的话,那么势必要在握手规则和消息处理handler之间进行耦合,如果那一天改变了一方的框架,就会导致另一方的改变,openssl的实现采用了后一种解决方案巧妙的解决了这个问题,不管怎样状态机顺序地在所有可能的状态中转变,每一个状态中调用固定的处理函数,然后在处理函数中采用了一个技巧:处理函数首先不去下面的BIO(一般是tcp套接字)中读消息,而是先检查一个标志reuse,如果这个标志置位了,那么就清除这个标志,取上次读到的消息后返回,在返回状态机处理handler之后,首先根据得到消息的头判断其类型是否是状态机处于该状态所处理消息的对应类型,如果不是,上述的reuse标志重新置位,handler返回,状态机推进到下一个状态,从而在接下来的状态处理当中不再从套接字读取消息。大致框架就是:
state_machine()
{
    while (true) {
        switch (state) 
            case s1:
                handler1(param);
                state = s2;
                break;
            case s2:
                handler2(param);
                state = s3;
                break;
            case s3:
                handler3(param);
                state = s4;
                break;
            ...
    }
}
如果s2状态所对应的消息是可选的,但是s3是必须的状态,并且此次此时并不需要处理s2这个状态,但是handler2中确实要读取消息,怎么办呢?如果真的从套接字读取消息的话,那么处理就会乱掉,handler2中读取的是s3状态所对应的消息,那么怎么处理呢?openssl的方式是在case s2中调用handler2之中判断一下当前读到消息的类型,如果不是对应于s2的消息,那就说明s2状态所对应的消息不存在,于是在case s2的handler中进行完判断之后,将一个reuse标志设置上,于是在s3状态中的s3处理中首先判断是否有reuse标志,如果有的话,那么就不再从套接字读取消息,而是将在handler2中读取的消息返回,并且清除reuse标志,之后以此类推。总结一下就是只要在可选消息的handler处理之后,都要进行类似s2处理的判断,如果失败就将reuse设置。
     但是这又引出了另外一个问题,那就是在每个handler中都要判断reuse标志,这个工作实在繁重,状态机处理是解放了,每个状态的handler的任务却更加繁重了,任何好的设计需要做到的就是尽量将繁杂的判断放到稳定的逻辑当中,状态机是不稳定的,因为有很多可选状态,每个状态的handler也不是稳定的,因为每个状态的处理太复杂了,随着版本的更新,任何细节的改变都会导致大量的更改,于是openssl就把这个逻辑抽取出来,专门放到SSL_METHOD的一个回调函数ssl3_get_message中,在调用完这个回调函数之后马上检查读到的消息类型是否是我们本状态要处理的类型,如果不是,那么马上将reuse设置为1,我们看一下ssl_get_message中在真正在套接字上读取消息之前的一个处理:
if (s->s3->tmp.reuse_message)
{
    s->s3->tmp.reuse_message=0;
    if ((mt >= 0) && (s->s3->tmp.message_type != mt)) {
        ...
    }
    *ok=1;
    s->init_msg = s->init_buf->data + 4;
    s->init_num = (int)s->s3->tmp.message_size;
    return s->init_num;
}
以上代码是s->method->ssl_get_message中首先要调用的,对应上面代码的另一部分在ssl3_connect中可选消息handler的调用之后,以ssl3_get_key_exchange为例:
int ssl3_get_key_exchange(SSL *s)
{
...
    n=s->method->ssl_get_message(s,
        SSL3_ST_CR_KEY_EXCH_A,
        SSL3_ST_CR_KEY_EXCH_B,
        -1,
        s->max_cert_list,
        &ok);
...
    if (s->s3->tmp.message_type != SSL3_MT_SERVER_KEY_EXCHANGE) {
        s->s3->tmp.reuse_message=1;
        return(1);
    }
...
}

openssl的这种实现使得握手状态机处理看起来十分清晰,所有的处理消息是否可选的逻辑都隐藏到了逻辑比较单一的回调函数中,然后在逻辑复杂的处理函数中简单判断即可,这种分离比SSLv2的设计要更好,在SSLv2的状态机实现中,每一个状态处理完了之后就会判断结果,然后根据结果去设置下一个状态,其实这种方式更像是一个状态机,SSLv3的实现反而不像状态机了,一个一个顺序的处理,没有变数,这种情况其实完全不用状态机实现,一个顺序执行的函数就可以了,连循环都不用,谁知道以后的版本会怎样呢。



 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1271994

相关文章
|
3月前
|
JSON 网络协议 物联网
MQTT协议问题之消息类型分类如何解决
MQTT协议是一个轻量级的消息传输协议,设计用于物联网(IoT)环境中设备间的通信;本合集将详细阐述MQTT协议的基本原理、特性以及各种实际应用场景,供用户学习和参考。
52 3
|
4月前
|
供应链 监控 网络安全
SAP ABAP 系统里的事务码 SMICM keep Alive 参数的含义和配置
SAP ABAP 系统里的事务码 SMICM keep Alive 参数的含义和配置
30 0
|
9月前
|
消息中间件 存储 缓存
RabbitMQ (HelloWord 消息应答 持久化 不公平分发 预取值)2
RabbitMQ (HelloWord 消息应答 持久化 不公平分发 预取值)2
44 0
|
9月前
|
消息中间件
RabbitMQ (HelloWord 消息应答 持久化 不公平分发 预取值)1
RabbitMQ (HelloWord 消息应答 持久化 不公平分发 预取值)1
43 0
|
11月前
|
消息中间件 JSON NoSQL
|
网络安全 数据库 网络协议
已成功与服务器建立连接,但是在登录过程中发生错误。 (provider: SSL Provider, error: 0 - 接收到的消息异常,或格式不正确。)
之前做好的asp.net部署后,发现 访问数据库时: 异常:已捕获: "已成功与服务器建立连接,但是在登录过程中发生错误。 (provider: SSL Provider, error: 0 - 接收到的消息异常,或格式不正确。
2067 0
|
消息中间件 JSON Java
RabbitTemplate 发送接受消息& amp 序列化机制|学习笔记
快速学习 RabbitTemplate 发送接受消息& amp 序列化机制
389 0
RabbitTemplate 发送接受消息& amp 序列化机制|学习笔记
|
消息中间件 网络协议 Java
RabbitMQ——消息手动应答、队列/消息持久化、不公平分发、预取值的概念理解及应用举例
RabbitMQ——消息手动应答、队列/消息持久化、不公平分发、预取值的概念理解及应用举例
RabbitMQ——消息手动应答、队列/消息持久化、不公平分发、预取值的概念理解及应用举例
|
C++ Python Java
protobuf 更新消息和扩展,包
一、更新一个消息类型 如果一个已有的消息格式已无法满足新的需求——如,要在消息中添加一个额外的字段——但是同时旧版本写的代码仍然可用。不用担心!更新消息而不破坏已有代码是非常简单的。
1176 0
|
缓存 SpringCloudAlibaba 应用服务中间件
SpringCloudAlibaba之Sentinel-规则管理及推送模式(pull&push)(下)
SpringCloudAlibaba之Sentinel-规则管理及推送模式(pull&push)
190 0