最近对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_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;
}
/* 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;
}
代码下载
https://github.com/fengliqiang