openssl在高性能网络框架中的使用(自定义BIO)

来源:互联网 发布:最好的远程桌面软件 编辑:程序博客网 时间:2024/05/22 03:32

最近对https有兴趣,所以决定开始学习使用openssl。

 所谓https, 是建立在安全通道之上的http协议。http 与 https的区别:

  普通http模式:

        http client ==> client socket ===> server socket ==> httpserver

   https 模式:

        http client ==> SSL client ==> client socket ===> serversocket ==> SSL server ==> http server

 为什么要搞openssl呢?

        对于高性能服务器的开发,很多人都封装了自己的框架。而且,很多人也希望能增加对https的支持。而自己开发一套ssl库,成本、风险都很高。

 

有什么问题?为什么不能直接使用?

        在复用能力、性能、和复杂度之间,各个框架都会有权衡。所以,与网络io相关的模块要想在这些框架中应用,还是有些麻烦的。不巧的是,openssl的常见使用方法就是使用openssl库里边内置的socketBIO。不过还好,路并没有被堵死,他是支持异步io的,这样,我们就可以通过替换掉socketBIO的办法来达到我们的目的。

先来说说openssl中BIO的机制:

       BIO工作模式是以一种链式方式工作的,每个BIO对象就是链条上的一个环节,接收上一个环节的调用,经过过滤,向下一个环节发起调用。调用的功能包括BIO对象创建,销毁,读、写、控制。如下图:


       这里的箭头就代表调用关系。包括读、写、控制。

       写数据自上而下是很自然的,通过SSL编码,然后BIO调用框架的接口发送数据,这是毫无疑问的。

       问题在读操作上。读的行为不该由应用发起。如果以这种方式工作,就会破坏大部分高性能的网络框架结构,导致低效的网络io。而且,也不符合网络数据传输的自然规则:此端的数据是否能收到和收到多少,取决于彼端发送者和传输链路!!!

 

那么我们需要一个什么样SSL层呢?

       对于一个应用来说,当然是越简单越好:设想有这样的SSL层,有数据要发的时候,调用SSL层的接口发出去;有数据进来的时候,SSL层调用应用提供的接口传进来,应用于SSL层之间都是明文通信。这样足够简单了吧。

       而对于网络框架来说,就是SSL层有数据要发,传给网络框架,有数据来了,调用SSL层的接口去处理。因为高性能的框架通常就是这样为应用提供服务的,SSL层对他来讲也是一个应用而已。所以,我们需要的SSL看起来像下面这个样子:

       箭头只表示数据的收发,其他诸如状态查询和控制操作,我们肯定有足够多的合理方式来处理,不需要在这个传输模式里增加不必要的负担。

       这种模式还有另外的好处:不管数据如何传输(即使你不用socket,只要数据传输无误),两端有SSL层保证,就能建立安全通道。

 

       现在,按照我们的想法,看看怎么把BIO的模式,转化成我们想要的filter模式。


       如何来做?

       1、  定义filter的接口和使用方法

       2、  设计一个BIO做SSL与我们filter之间的桥梁

       3、  做一个filter,封装SSL和我们自定义的BIO

        4、  需要使用SSL的地方,我们只要使用这个写好的filter。

 

好,开干吧

一、定义filter的接口和使用方法:

/* interface/filter.h *//* Copyright (C) 2013 fengliqiang (mr.fengliqiang@gmail.com) * All rights reserved. * */#pragma oncenamespace frames {namespace filter {class I_pin {public:virtual ~I_pin(){}virtual void on_data(const char *data, int len) = 0;};class I_pout {I_pin *_pin;public:I_pout():_pin(0){}virtual ~I_pout(){}I_pin *pin() const { return _pin; }void set_pin(I_pin *p) {_pin = p; }virtual void write(const char *data, int len) = 0;};class I_filter :public I_pin, public I_pout {I_pout *_next;protected:I_pout *next() { return _next; }public:I_filter():_next(0){}virtual ~I_filter(){}virtual void connect(I_pout *out) { if ( _next = out ) out->set_pin(this); }};}}


        代码说明:

        I_pin接口:
        作为一个filter的上层,需要提供一个接收数据的接口,I_pin就是这个接口。接口只有一个方法,就是on_data,数据的接收者必需要实现这个接口,处理数据。

        I_pout接口:
        一个filter要处理上层传来的数据,so, 得有一个write方法, so,有了write.....
前一个filter和后一个要连接起来,so, I_pout 里有了set_pin方法。或许你会在两个filter之间插入一个filter, so, 有了pin()方法….

        作为filter链的顶层,可以只实现一个I_pin接口;同样,最底层,可以只实现一个I_pout接口。
而中间的filter,必须实现两个接口,方便起见,提供了I_filter

        使用:
        顶层模块连接第一个filter, 使用I_pout::set_pin(this);
        Filter连接下层filter或者filter链底层,使用connect;

二、设计一个BIO做SSL与我们filter之间的桥梁:


/* bio_proxy.h *//* Copyright (C) 2013 fengliqiang (mr.fengliqiang@gmail.com) * All rights reserved. * */#pragma once#include <openssl/ssl.h>namespace bio_proxy {class I_io {public:virtual ~I_io(){}virtual int proxy_read(char *buf, int size) = 0;virtual int proxy_write(const char *buf, int size) = 0;};bool proxy_init(SSL *ssl, I_io *proxy);}

        这是给我们自定义BIO提供的数据收发接口, read和write必须提供。


/* bio_proxy.cpp *//* Copyright (C) 2013 fengliqiang (mr.fengliqiang@gmail.com) * All rights reserved. * */#include "bio_proxy.h"#include <openssl/bio.h>#ifdef _MSC_VER#pragma comment(lib, "libeay32.lib")#pragma comment(lib, "ssleay32.lib")#endifstatic int proxy_create(BIO *bio){bio->init = 0;bio->num = 0;bio->ptr = 0;bio->flags = 0;return 1;}static int proxy_destroy(BIO *bio){if ( bio == 0 ) return 0;if ( bio->shutdown ) {bio->init = 0;bio->flags = 0;}return 1;}static int proxy_read(BIO *bio, char *buf, int len){if ( len == 0 ) return 0;bio_proxy::I_io *sink = (bio_proxy::I_io*)bio->ptr;int ret = sink->proxy_read(buf, len);BIO_clear_retry_flags(bio);if ( ret <= 0 ) BIO_set_retry_read(bio);return ret;}static int proxy_write(BIO *bio, const char *buf, int len){if ( len == 0 ) return 0;bio_proxy::I_io *sink = (bio_proxy::I_io*)bio->ptr;int ret = sink->proxy_write(buf, len);BIO_clear_retry_flags(bio);if ( ret <= 0 ) BIO_set_retry_read(bio);return ret;}static long proxy_ctrl(BIO *bio, int cmd, long num, void *ptr){switch (cmd) {case BIO_C_SET_FD:proxy_destroy(bio);bio->num = 0;bio->ptr = ptr;bio->shutdown = (int)num;bio->init = 1;return 1;case BIO_C_GET_FD:if ( bio->init ) {if ( ptr ) *( (int *)ptr ) = bio->num;return bio->num;}else return -1;case BIO_CTRL_GET_CLOSE:return bio->shutdown;case BIO_CTRL_SET_CLOSE:bio->shutdown = (int)num;return 1;case BIO_CTRL_DUP:case BIO_CTRL_FLUSH:return 1;default:break;}return 0;}static int proxy_puts(BIO *bp, const char *str){return proxy_write(bp, str, strlen(str));}static BIO_METHOD proxy_methods ={BIO_TYPE_SOURCE_SINK | 0x80,"asio_proxy",proxy_write,proxy_read,proxy_puts,0,//proxy_gets,proxy_ctrl,proxy_create,proxy_destroy,0,};bool bio_proxy::proxy_init(SSL *s, bio_proxy::I_io *proxy){BIO *bio = BIO_new(&proxy_methods);if ( bio == 0 ) return false;proxy_ctrl(bio, BIO_C_SET_FD, BIO_NOCLOSE, (void*)proxy);SSL_set_bio(s,bio,bio);return true;}

     参考socketBIO实现做的

       BIO结构中有个ptr,是给BIO实现者使用的域,实现者可以用这个这个指针存放与BIO功能相关的对象或数据。我们用这个指针存储BIO数据的收发代理对象,这个代理就是我们实现的filter。

三、做一个filter,封装SSL和我们自定义的BIO


/* C_ssl_protocol.h *//* Copyright (C) 2013 fengliqiang (mr.fengliqiang@gmail.com) * All rights reserved. * */#pragma once#include <buffer/cache_buffer.h>#include "bio_proxy.h"class C_ssl_protocol :public ssl_protocol::I_ssl_filter, public bio_proxy::I_io{C_cache_buffer<> _in_cache;C_cache_buffer<> _out_cache;bool _connected;SSL *_ssl;private:const char *_in_data;int _in_size;bool _is_server;private:virtual int proxy_read(char *buf, int size);virtual int proxy_write(const char *buf, int size);public:C_ssl_protocol(SSL_CTX *ctx, bool b_server = false);virtual ~C_ssl_protocol(void);virtual void write(const char *data, int len);virtual void on_data(const char *data, int len);virtual void destroy() { delete this; }virtual void loop();bool fine() const{ return _ssl != 0; }};

/* C_ssl_protocol.cpp *//* Copyright (C) 2013 fengliqiang (mr.fengliqiang@gmail.com) * All rights reserved. * */#include "C_ssl_protocol.h"#include <openssl/rand.h>#include <openssl/err.h>#include <common/atom.h>#include <string>C_ssl_protocol::C_ssl_protocol(SSL_CTX *ctx, bool b_server):_connected(false), _is_server(b_server){_ssl = SSL_new(ctx);if ( _ssl ) {if ( ! bio_proxy::proxy_init(_ssl, this) ) {printf("init error !\n");SSL_free(_ssl);_ssl = 0;}}}C_ssl_protocol::~C_ssl_protocol(void){if ( _ssl ) {SSL_shutdown(_ssl); SSL_free(_ssl); }}void C_ssl_protocol::loop(){on_data(0, 0);write(0, 0);}void C_ssl_protocol::write(const char *data, int len){if ( _connected ) {while (_out_cache.size() ) {char buffer[1024 * 10];int size = _out_cache.pop(buffer, sizeof(buffer));int write_size = SSL_write(_ssl, buffer, size);assert(write_size = size);}if ( len ) {int size = SSL_write(_ssl, data, len);assert(size == len);}}else {_out_cache.push(data, len);}}void C_ssl_protocol::on_data(const char *data, int len){_in_data = data;_in_size = len;if ( ! _connected ) {_connected = (_is_server? SSL_accept(_ssl): SSL_connect(_ssl)) > 0;}if ( _connected && ( _in_size || _in_cache.size() ) ) {while ( true ) {char buffer[1024 * 10];int size = SSL_read(_ssl, buffer, sizeof(buffer));if ( size <= 0 ) break;if ( pin() ) pin()->on_data(buffer, size);}}if ( _in_size ) {_in_cache.push(_in_data, _in_size);_in_size = 0;}}int C_ssl_protocol::proxy_read(char *buf, int size){int ret_size = 0;if ( _in_cache.size() ) ret_size += _in_cache.pop(buf, size);if ( ret_size < size ) {int min_read = (std::min)(size - ret_size, _in_size);if ( min_read ) memcpy(buf + ret_size, _in_data, min_read);_in_size -= min_read;_in_data += min_read;ret_size += min_read;}return ret_size ? ret_size: -1;}int C_ssl_protocol::proxy_write(const char *buf, int size){if ( next() ) next()->write(buf, size);return size;}

C_cache_buffer是一个流式的缓冲器,用来缓存发送接收过程中的数据,代码太长,就不贴了。


代码下载

     源代码和测试代码放到github上了,地址

https://github.com/fengliqiang