网络通信管理
正如网络通信概述中所解释的,SmartEDB 7.1.1795 及更高版本允许使用 IPv6 地址协议来指定节点地址。
以下部分描述了对于将使用了 SmartEDB 早期版本网络通信的应用程序移植到新版本的开发人员来说可能重要的问题。
IPv4 vs. IPv6 问题
在网络地址以<host>:<port>
形式表示的地方,IPv6 地址必须用方括号括起来,例如:[::1]:5000
或 [fe80::21e:4fff:fe9c:5398%1]:5000
。这是为了避免与 IPv6 地址中的 :
发生冲突。
在 IP 地址以 uint4
表示的地方,类型已从 uint4
更改为 struct mco_inetaddr_t
。此结构用于表示 IPv4 或 IPv6 地址。 进行网络名称解析时,SmartEDB 网络层依赖于目标系统上的 getaddrinfo()
函数及其设置。唯一的例外是在 Windows 上启用双协议栈时。
API 更改
从先前版本到 SmartEDB 7.1.1795 版本的 C API 更改的完整列表如下:
函数
uint4 mco_HA_get_ip_addr(mco_channel_h chan)
已弃用。- 作为替代方案添加了函数
void mco_HA_get_inet_addr(mco_channel_h chan, mco_inetaddr_t *addr)
。
- 作为替代方案添加了函数
SqlServer::session_info_t::peer_addr
和SqlServer::query_info_t::peer_addr
的类型已从 uint4 更改为mco_inetaddr_t
。为
SqlServer
类添加了一个新的构造函数。它接受SqlServer::OpenParameters
结构作为参数,该结构结合了所有现有参数,并添加了一个新的参数const char *netInterface
,该参数是监听的网络接口地址。例如:
"0.0.0.0" (IPv4 ADDR_ANY)
或
"[::1]" (IPv6 localhost)
内部 API 更改(在 mconet.h 中):
在枚举 MCO_SOCK_DOMAIN
中新增了常量 MCO_SOCK_INETV4_DOMAIN
和 MCO_SOCK_INETV6_DOMAIN
。这些常量允许明确地将套接字定义为仅 IPv4 或仅 IPv6。旧值 MCO_SOCK_INET_DOMAIN
(默认值)创建的套接字,其域由网络地址(在 connect()
或 bind()
时)确定。
以下结构被添加用于表示 IP 地址(IPv4 和 IPv6):
typedef struct mco_inetaddr_t_
{
union
{
uint1 u1[16];
uint2 u2[8];
uint4 u4[4];
};
uint4 scope_id;
} mco_inetaddr_t;
新增了以下功能:
MCO_RET mco_net_parse_address(
const char *address,
char *hostname,
int hostname_len,
int *port,
const char * *endptr
);
这个函数允许以<host>:<port>
的形式解析地址字符串(能识别 IPv6 地址)。函数参数如下:
const char *address
:地址字符串char *hostname
:地址中<host>
部分的缓冲区int hostname_len
:主机名缓冲区的大小int *port
:解析出的端口号const char * *endptr
:如果endptr
不为 NULL,该函数会将 IP 地址之后的第一个字符的地址存储在*endptr
中。(这可用于在加载 IP 地址后继续解析)
新增了以下函数用于将 mco_inetaddr_t 结构体转换为字符串表示形式。由 str
指向的缓冲区长度必须至少为 MCO_NET_INETADDR_STRING_LEN
。
mco_socket_t::connect()
被改为:
int (*connect)(
struct mco_socket_t *s,
char const* host,
int port,
timer_unit timeout
);
/* 旧版 */
int (*connect)(
struct mco_socket_t *s,
char const* host,
int port,
int max_attempts,
timer_unit delay
);
mco_socket_t::get_peer_info()
被改为:
int (*get_peer_info)(
struct mco_socket_t* s,
mco_peer_info_t *pinfo
);
/* 旧版 */
int (*get_peer_info)(
struct mco_socket_t* s,
uint4 *ip,
int *port
);
其中结构体 mco_peer_info_t 定义为
typedef struct mco_peer_info_t_
{
mco_inetaddr_t ip;
int port;
mco_size_t rx_bytes;
mco_size_t tx_bytes;
} mco_peer_info_t;
除了 IP 和端口之外,它还包含两个计数器 rx_bytes
和 tx_bytes
,这两个计数器能够确定发送或接收至线路的字节数。(对于压缩套接字,这些是压缩数据的大小)。
压缩可以与 SSL 加密结合使用——在这种情况下,数据先被压缩,然后再进行加密。
compression_level
参数也被添加到了所有使用 mco_socket_t
的 SmartEDB 模块(即高可用性、集群、物联网、RSQL)的相应结构和 API 函数中。
所有通信部分必须使用相同的 compression_level
参数值。
如果未使用压缩(压缩级别设置为 0),则无需 Zlib 库。
新增了常量 MCO_NET_CAP_IPV6
。如果支持 IPv6,函数 mco_net_get_capabilities()
将返回此选项。文件 mcocfg.h
中的新宏 MCO_CFG_NET_SUPPORT_IPV6
启用 IPv6 支持(默认启用)。
SSL支持
SmartEDB 运行时(通过 mconet 库)动态加载 OpenSSL 二进制库。要构建具有 OpenSSL 支持的 mconet
库,应将环境变量 MCO_OPENSSL_INCLUDE_PATH
设置为 OpenSSL 包含目录,例如:
make x64=on DEBUG=on MCO_OPENSSL_INCLUDE_PATH=/home/user/OpenSSL/include host target
在 Windows 系统上,首先需要安装 OpenSSL 1.0.2 版本的库文件,可以从此处下载。然后使用如下命令定义环境变量:
set MCO_OPENSSL_INCLUDE_PATH=c:\OpenSSL-Win64\include
关于 Mac OS X 的 mcossl
在撰写本文时,最新稳定的 Mac OS X 版本自带的是 OpenSSL 0.9.8,而 libmcossl 则需要 OpenSSL 1.1.1。因此,用户必须手动获取并构建 OpenSSL 1.1.1。在构建 SmartEDB 库时,必须按照上述说明设置 MCO_OPENSSL_INCLUDE_PATH 环境变量。此外,DYLD_LIBRARY_PATH 环境变量必须指向包含 OpenSSL 1.1.1 库的目录。否则,xSQL(以及启用 mcossl 的应用程序)将无法启动。
用法
为了在应用程序中顺利使用 OpenSSL 网络功能,您需要确保系统已安装本地的 OpenSSL,并且编译器能够访问 OpenSSL 的头文件和库文件。
在开始之前,请通过调用 mco_ssl_init()
函数来初始化 OpenSSL 子系统。当不再需要时,记得调用 mco_ssl_cleanup()
函数进行清理。此外,我们还提供了两个辅助函数:
mco_ssl_params_init()
用于初始化mco_ssl_params_t
结构并设置默认值;mco_ssl_load_verify_locations()
则允许您从指定的文件或目录加载证书颁发机构(CA)证书。
有关这些参数的具体配置方法,请参考 OpenSSL 参数页面。
要启用 OpenSSL 安全通信,您需要将 SSL 设置传递给 SmartEDB 子系统。具体来说,在配置 SmartEDB 高可用性结构 mco_HA_master_params_t
或 SmartEDB 集群结构 mco_clnw_tcp_params_t
时,以及在创建 RemoteSqlEngine、DistributedSqlEngine 和 SqlServer C++ 类实例时,都需要提供一个指向正确配置的 mco_ssl_params_t
结构的指针。如果将 ssl_params
指针设置为 NULL
,则会使用普通的 TCP 通信方式。
请注意,不要释放 mco_ssl_params_t
结构,因为 SmartEDB 运行时会保存该结构的引用,以确保其在整个运行期间保持有效。
C API functions
SmartEDB OpenSSL API 函数如下:
MCO_RET mco_ssl_init()
初始化全局 OpenSSL 上下文,并为多线程应用程序设置所需的同步原语。
void mco_ssl_cleanup();
释放全局 OpenSSL 上下文和同步原语。
void mco_ssl_params_init(mco_ssl_params_t *params);
设置 mco_ssl_params_t
结构的默认值。
MCO_RET mco_ssl_load_verify_locations(
const char *ca_file,
const char *ca_path
);
请使用 c_rehash
实用程序设置 ca_path
中指定目录的内容。
关于加载 CA 验证位置,请参阅 SSL_CTX_load_verify_locations()
。
快速开始
要在您的应用程序中启用 OpenSSL 支持,请按照以下步骤操作:
将您的应用程序与库
libmconet
(在 Windows 上为mconet.lib
)链接;调用
mco_ssl_init()
;调用
mco_ssl_load_verify_locations()
加载 CA 证书,否则对等验证可能会失败。(详情请参阅 OpenSSL 手册页)分配并初始化 mco_ssl_params_t 结构。在将其传递给 SmartEDB 运行时后,切勿释放该结构。
至少设置以下参数:
tmp_dh
: 详情及配置说明请参阅 OpenSSL 手册页(包含了可用于此目的的 dhparam 实用程序);certificate
或certificate_file
;private_key,
或private_key_file
.- 对于 SmartEDB 高可用性应用程序,请在 mco_HA_master_params_t 的
ssl_params
参数中传递指向 mco_ssl_params_t 结构的指针; - 对于 SmartEDB 集群应用程序,请在
mco_clnw_tcp_params_t
的ssl_params
参数中传递指向 mco_ssl_params_t 结构的指针; - 对于 RemoteSQL 应用程序,请在 C++ 类 RemoteSqlEngine、DistributedSqlEngine 或 SqlServer 的构造函数的 ssl_params 参数中传递指向
mco_ssl_params_t
结构的指针。
必须特别注意的是,尽管默认的 mco_ssl_params_t 值是经过精心选择以提供最佳安全性,但对所有设置进行仔细审查和深入理解对于实现安全可靠的通信至关重要。
教程
有关使用 OpenSSL 生成自签名证书的教程有很多。
复杂教程:https://jamielinux.com/docs/openssl-certificate-authority/create-the-root-pair.html.
简单教程:http://datacenteroverlords.com/2012/03/01/creating-your-own-ssl-certificate-authority/.
调试 OpenSSL
调试 OpenSSL 应用程序可能颇具难度。可以采用几种方法。 -
- 若要调试客户端功能,请使用 OpenSSL 的 s_server 实用程序启动服务器,并从您的应用程序连接到该服务器。将
-debug
、-msg
和-state
参数传递给此实用程序以获取更详细的输出,并查找错误消息; - 或者,使用 OpenSSL 的 s_client 实用程序与您的应用程序建立连接。传递
-debug
、-msg
和-state
参数以获取更详细的输出; - 作为临时措施,可以将
mco_ssl_params_t
中的verify_mode
设置为MCO_SSL_VERIFY_NONE
以禁用对等验证。如果应用程序开始运行,则证书和/或私钥可能设置不正确,或者 CA 证书未设置。完成后请记得重新启用对等验证。
实现细节
为避免对现有的 SmartEDB 代码库进行大规模改动,并确保其能够在不依赖 OpenSSL 的情况下构建,SSL 设置mco_ssl_params_t
通过空指针传递给 SmartEDB 高可用性子系统。
目前,SmartEDB 尚不支持 UDP SSL 加密及本地套接字通信。因此,通过 UDP 套接字(包括多播套接字)传输的任何应用程序数据均未加密。
mconet
库负责创建 TCP 套接字并建立 TCP 连接。一旦 TCP 连接成功建立,系统将尝试执行 SSL/TLS 握手。握手成功后,将建立 SSL 连接以用于后续通信。
尽管在官方文档中并未明确指出,但 OpenSSL 的 SSL_read()
和 SSL_write() 函数不能在同一套接字上从不同线程同时调用。因此,所有读写操作都由互斥锁保护,以确保线程安全。有关详细信息,请参阅 Stack Overflow 相关讨论。
在现有的 SmartEDB 高可用性和集群代码库中,某些进程使用 select()
调用来等待非阻塞 TCP 套接字的连接建立。然而,在 SSL 连接中,这种方法并不适用:虽然 TCP 连接可以异步建立,但在非阻塞套接字上完成 SSL 握手需要一系列的 SSL_read()
和 SSL_write()
调用。因此,当 TCP 连接建立(异步)时,SSL 握手是以阻塞方式完成的。
为了确保多线程应用程序的正确运行,OpenSSL 需要设置多个同步原语。这些原语在 mco_ssl_init()
函数中初始化,并且必须在执行任何 SSL 操作之前调用该函数。随后,mco_ssl_cleanup()
函数会释放这些同步原语。