问题:一个简单的用例分析。
环境:
[root@localhost ~/test/ssl_test]
# uname -a
Linux localhost.localdomain 3.10.0-123.el7.x86_64 #1 SMP Mon Jun 30 12:09:22 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
拓扑:localhost
±---------------------------------------------------------------+
client ---------127.0.0.1/2020----------> server
±---------------------------------------------------------------+
Server端:
借助于nc工具;
Client端:
#include
#include
#include
#include
#include
#include
#include
static void test_ssl_socket(int socket)
{
char buf[8192] = {0};
int ret = 0;
SSL_library_init();
SSL_load_error_strings();
SSL_CTX *ctx = SSL_CTX_new(SSLv23_client_method());
if (ctx) {
SSL *ssl = SSL_new(ctx);
if (ssl) {
ret = SSL_set_fd(ssl, socket);
printf("SSL_set_fd: %d\n", ret);
ret = SSL_connect(ssl);
printf("SSL_connect: %d\n", ret);
printf("======send=====\n");
strncpy(buf, "I am client", strlen(buf));
ret = SSL_write(ssl, buf, strlen(buf));
printf("%d bytes sent\n", ret);
printf("======recv=====\n");
memset(buf, 0, sizeof(buf));
ret = SSL_read(ssl, buf, sizeof(buf));
printf("%d bytes read\n", ret);
if (ret > 0) {
printf("%s\n", buf);
}
SSL_free(ssl);
}
SSL_CTX_free(ctx);
}
}
int main(int argc, char *const argv[])
{
int socket_fd = -1;
const char *ip = NULL;
unsigned short port = 0;
struct sockaddr_in svraddr;
int ret = -1;
ip = argv[1];
port = atoi(argv[2]);
printf("IP: %s, port: %d\n", argv[1], port);
/* 创建一个 socket 用于 tcp 通信 */
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket < 0) {
perror("Error: Failed to create socket\n");
exit(errno);
}
printf("socket created\n");
bzero(&svraddr, sizeof(svraddr));
svraddr.sin_family = AF_INET;
svraddr.sin_addr.s_addr = inet_addr(ip);
svraddr.sin_port = htons(port);
ret = connect(socket_fd, (struct sockaddr *)&svraddr, sizeof(svraddr));
if (ret < 0) {
printf("create socket failed\n");
return -1;
}
test_ssl_socket(socket_fd);
printf("OK\n");
return 0;
}
编译:
[root@localhost ~/test/ssl_test]
# gcc ./ssl_client.c -o ssl_client -lssl
运行:
(1)server执行
# nc -l 2020
(2)client执行
# ./ssl_client 127.0.0.1 2021
IP: 127.0.0.1, port: 2021
socket created
SSL_set_fd: 1
/*长时间等待...*/
问题:
Q1:客户端为什么长时间等待?
A1:客户端停留在SSL_connect()处,并尝试建立ssl连接,对端使用nc开启非ssl连接,导致client端ssl连接失败。
Q2:客户端如果连接不成功,是否可以超时退出。
A2: 提供一个思路,使用定时器,当定时器时间到达时,在定时器的回调函数中释放ssl和ctx,这样在SSL_connect()中会及时返回。
#include
#include
#include
#include
#include
#include
#include
#include
#include
int socket_fd = -1;
SSL_CTX *ctx = NULL;
SSL *ssl = NULL;
static void test_ssl_socket(int socket)
{
char buf[8192] = {0};
int ret = 0;
SSL_library_init();
SSL_load_error_strings();
ctx = SSL_CTX_new(SSLv23_client_method());
if (ctx) {
ssl = SSL_new(ctx);
if (ssl) {
ret = SSL_set_fd(ssl, socket);
printf("SSL_set_fd: %d\n", ret);
ret = SSL_connect(ssl);
printf("SSL_connect: %d\n", ret);
if (ret < 0) {
printf("Error: Failed to create ssl connect\n");
return;
}
printf("SSL_connect: %d\n", ret);
printf("======send=====\n");
strncpy(buf, "I am client", strlen(buf));
ret = SSL_write(ssl, buf, strlen(buf));
printf("%d bytes sent\n", ret);
printf("======recv=====\n");
memset(buf, 0, sizeof(buf));
ret = SSL_read(ssl, buf, sizeof(buf));
printf("%d bytes read\n", ret);
if (ret > 0) {
printf("%s\n", buf);
}
if (ssl != NULL) {
printf("free SSL.\n");
SSL_free(ssl);
}
}
if (ctx != NULL) {
printf("free CTX.\n");
SSL_CTX_free(ctx);
}
}
}
void free_ssl_connect(int sig)
{
printf("10s reached.\n");
printf("free SSL.\n");
SSL_free(ssl);
printf("free CTX.\n");
SSL_CTX_free(ctx);
}
int main(int argc, char *const argv[])
{
const char *ip = NULL;
unsigned short port = 0;
struct sockaddr_in svraddr;
int ret = -1;
/***timer***/
signal(SIGALRM, free_ssl_connect);
alarm(10);
/******/
ip = argv[1];
port = atoi(argv[2]);
printf("IP: %s, port: %d\n", argv[1], port);
/* 创建一个 socket 用于 tcp 通信 */
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket < 0) {
perror("Error: Failed to create socket\n");
exit(errno);
}
printf("socket created\n");
bzero(&svraddr, sizeof(svraddr));
svraddr.sin_family = AF_INET;
svraddr.sin_addr.s_addr = inet_addr(ip);
svraddr.sin_port = htons(port);
ret = connect(socket_fd, (struct sockaddr *)&svraddr, sizeof(svraddr));
if (ret < 0) {
printf("Error:Create socket failed\n");
return -1;
}
test_ssl_socket(socket_fd);
printf("free fd.\n");
if (socket_fd != -1) {
printf("free fd\n");
close(socket_fd);
}
printf("OK\n");
return 0;
}
优化:
#include
#include
#include
#include
#include
#include
#include
#include
#include
int socket_fd = -1;
SSL_CTX *ctx = NULL;
SSL *ssl = NULL;
static void free_ssl_connect(int sig)
{
if (sig != SIGALRM) {
printf("sig != SIGALRM\n");
return ;
}
printf("10s timer reached.\n");
if (ssl != NULL) {
printf("free SSL.\n");
SSL_free(ssl);
}
if (ctx != NULL) {
printf("free CTX.\n");
SSL_CTX_free(ctx);
}
}
static void close_ssl_connect_timer(void)
{
printf("close 10s timer\n");
alarm(0);
}
static void open_ssl_connect_timer(void)
{
/***timer***/
printf("open 10s timer\n");
signal(SIGALRM, free_ssl_connect);
alarm(10);
}
static void test_ssl_socket(int socket)
{
char buf[8192] = {0};
int ret = 0;
SSL_library_init();
SSL_load_error_strings();
ctx = SSL_CTX_new(SSLv23_client_method());
if (ctx) {
ssl = SSL_new(ctx);
if (ssl) {
ret = SSL_set_fd(ssl, socket);
printf("SSL_set_fd: %d\n", ret);
/*open ssl connect timer.*/
open_ssl_connect_timer();
ret = SSL_connect(ssl);
printf("SSL_connect: %d\n", ret);
if (ret < 0) {
printf("return\n");
return;
}
/*close ssl connect timer.*/
close_ssl_connect_timer();
printf("SSL_connect: %d\n", ret);
printf("======send=====\n");
strncpy(buf, "I am client", strlen(buf));
ret = SSL_write(ssl, buf, strlen(buf));
printf("%d bytes sent\n", ret);
printf("======recv=====\n");
memset(buf, 0, sizeof(buf));
ret = SSL_read(ssl, buf, sizeof(buf));
printf("%d bytes read\n", ret);
if (ret > 0) {
printf("%s\n", buf);
}
if (ssl != NULL) {
printf("free SSL.\n");
SSL_free(ssl);
ssl = NULL;
}
}
if (ctx != NULL) {
printf("free CTX.\n");
SSL_CTX_free(ctx);
ctx = NULL;
}
}
}
int main(int argc, char *const argv[])
{
const char *ip = NULL;
unsigned short port = 0;
struct sockaddr_in svraddr;
int ret = -1;
if (argc != 3) {
printf("Error: params error\n");
return -1;
}
ip = argv[1];
port = atoi(argv[2]);
printf("IP: %s, port: %d\n", argv[1], port);
/* 创建一个 socket 用于 tcp 通信 */
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket < 0) {
perror("Error: Failed to create socket\n");
exit(errno);
}
printf("socket created\n");
bzero(&svraddr, sizeof(svraddr));
svraddr.sin_family = AF_INET;
svraddr.sin_addr.s_addr = inet_addr(ip);
svraddr.sin_port = htons(port);
ret = connect(socket_fd, (struct sockaddr *)&svraddr, sizeof(svraddr));
if (ret < 0) {
printf("create socket failed\n");
return -1;
}
test_ssl_socket(socket_fd);
printf("free fd.\n");
if (socket_fd != -1) {
printf("free fd.\n");
close(socket_fd);
socket_fd = -1;
}
printf("OK\n");
while (1);
return 0;
}
遗留问题:
要注意的是,一个进程只能有一个闹钟时间,如果在调用alarm之前已设置过闹钟时间,则任何以前的闹钟时间都被新值所代替。