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

最近对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 ([email protected])
 * All rights reserved.
 *
 */
#pragma once
namespace 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 ([email protected])
 * All rights reserved.
 *
 */
#pragma once
#include 
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 ([email protected])
 * All rights reserved.
 *
 */
#include "bio_proxy.h"
#include 
#ifdef _MSC_VER
#pragma comment(lib, "libeay32.lib")
#pragma comment(lib, "ssleay32.lib")
#endif
static 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 ([email protected])
 * All rights reserved.
 *
 */
#pragma once
#include 
#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 ([email protected])
 * All rights reserved.
 *
 */
#include "C_ssl_protocol.h"
#include 
#include 
#include 
#include 

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




你可能感兴趣的:(openssl,c++)