Files
TLS/tls_client.c
2025-10-09 10:50:30 +08:00

1156 lines
39 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @file tls_client.c
* @brief TLS 1.2/1.3 客户端实现
* @author Assistant
* @date 2024
* @version 1.0.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>
#include <time.h>
#define MAX_BUFFER_SIZE 4096
#define TEST_DATA_SIZE 1024
#define MAX_RETRIES 3
#define CONNECT_TIMEOUT 10
// 全局SSL上下文
SSL_CTX *ssl_ctx = NULL;
/**
* @brief 格式化时间戳为易读格式
*/
void print_timestamp(time_t timestamp) {
struct tm *tm_info;
char time_str[32];
tm_info = gmtime(&timestamp);
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S UTC", tm_info);
printf("[%s]", time_str);
}
/**
* @brief SSL信息回调函数
*/
void ssl_info_callback(const SSL *ssl, int type, int val) {
const char *str;
static time_t last_time = 0;
time_t current_time;
// 检查SSL对象是否有效
if (!ssl) {
printf("SSL回调: SSL对象为空\n");
return;
}
current_time = time(NULL);
switch (type) {
case SSL_CB_LOOP:
str = SSL_state_string_long(ssl);
if (str) {
// 计算与上次状态的时间差
if (last_time > 0) {
double time_diff = difftime(current_time, last_time);
if (time_diff > 0.5) { // 只显示超过0.5秒的延迟
print_timestamp(current_time);
printf(" SSL状态: %s (延迟: %.1f秒)\n", str, time_diff);
} else {
print_timestamp(current_time);
printf(" SSL状态: %s\n", str);
}
} else {
print_timestamp(current_time);
printf(" SSL状态: %s\n", str);
}
last_time = current_time;
}
break;
case SSL_CB_ALERT:
str = SSL_alert_type_string_long(val);
if (str) {
print_timestamp(current_time);
printf(" SSL警告: %s\n", str);
}
break;
case SSL_CB_EXIT:
if (val == 0) {
str = SSL_state_string_long(ssl);
if (str) {
print_timestamp(current_time);
printf(" SSL退出: %s\n", str);
}
} else if (val < 0) {
str = SSL_state_string_long(ssl);
if (str) {
print_timestamp(current_time);
printf(" SSL错误: %s\n", str);
}
}
break;
case SSL_CB_HANDSHAKE_START:
print_timestamp(current_time);
printf(" SSL握手开始\n");
last_time = current_time;
break;
case SSL_CB_HANDSHAKE_DONE:
print_timestamp(current_time);
printf(" SSL握手完成\n");
break;
default:
// 过滤掉重复的4097类型回调
if (type != 4097) {
print_timestamp(current_time);
printf(" SSL回调: 类型=%d, 值=%d\n", type, val);
}
break;
}
}
/**
* @brief SSL消息回调函数
*/
void ssl_msg_callback(int write_p, int version, int content_type, const void *buf, size_t len, SSL *ssl, void *arg) {
const char *direction = write_p ? "发送" : "接收";
const char *content_name = "";
time_t current_time;
// 检查参数有效性
if (!buf || len == 0) {
return;
}
current_time = time(NULL);
switch (content_type) {
case SSL3_RT_CHANGE_CIPHER_SPEC:
content_name = "ChangeCipherSpec";
break;
case SSL3_RT_ALERT:
content_name = "Alert";
break;
case SSL3_RT_HANDSHAKE:
content_name = "Handshake";
break;
case SSL3_RT_APPLICATION_DATA:
content_name = "ApplicationData";
break;
default:
content_name = "Unknown";
break;
}
// 只显示重要的握手消息过滤掉重复的Unknown消息
if (content_type == SSL3_RT_HANDSHAKE || content_type == SSL3_RT_CHANGE_CIPHER_SPEC) {
print_timestamp(current_time);
printf(" SSL消息: %s %s (版本: %d, 长度: %zu)\n", direction, content_name, version, len);
}
}
/**
* @brief 初始化OpenSSL库
* @param debug_level 调试级别 (0=无调试, 1=基本, 2=详细, 3=完整)
*/
void init_openssl(int debug_level) {
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
if (debug_level > 0) {
// 启用OpenSSL调试
if (debug_level >= 1) {
printf("启用OpenSSL基本调试\n");
}
if (debug_level >= 2) {
printf("启用OpenSSL详细调试\n");
}
if (debug_level >= 3) {
printf("启用OpenSSL完整调试\n");
}
}
}
/**
* @brief 清理OpenSSL资源
*/
void cleanup_openssl() {
if (ssl_ctx) {
SSL_CTX_free(ssl_ctx);
}
EVP_cleanup();
ERR_free_strings();
}
/**
* @brief 创建SSL上下文
* @param cert_file 客户端证书文件路径
* @param key_file 客户端私钥文件路径
* @param ca_file CA证书文件路径
* @param verify_peer 是否验证服务器证书
* @param tls_version TLS版本 (1=TLS1.2, 2=TLS1.3, 0=自动)
* @return SSL_CTX* SSL上下文指针失败返回NULL
*/
SSL_CTX* create_ssl_context(const char* cert_file, const char* key_file, const char* ca_file, int verify_peer, int tls_version, int debug_level) {
SSL_CTX *ctx;
// 创建SSL上下文支持TLS 1.2和1.3
ctx = SSL_CTX_new(TLS_client_method());
if (!ctx) {
fprintf(stderr, "无法创建SSL上下文\n");
return NULL;
}
// 设置调试回调(如果启用调试)
if (debug_level >= 2) {
SSL_CTX_set_info_callback(ctx, ssl_info_callback);
}
if (debug_level >= 3) {
SSL_CTX_set_msg_callback(ctx, ssl_msg_callback);
}
// 根据指定版本设置TLS版本
if (tls_version == 1) {
// 只使用TLS 1.2
if (SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION) != 1 ||
SSL_CTX_set_max_proto_version(ctx, TLS1_2_VERSION) != 1) {
fprintf(stderr, "设置TLS 1.2版本失败\n");
SSL_CTX_free(ctx);
return NULL;
}
printf("配置为TLS 1.2模式\n");
} else if (tls_version == 2) {
// 只使用TLS 1.3
if (SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION) != 1 ||
SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION) != 1) {
fprintf(stderr, "设置TLS 1.3版本失败\n");
SSL_CTX_free(ctx);
return NULL;
}
printf("配置为TLS 1.3模式\n");
} else {
// 自动模式支持TLS 1.2和1.3
if (SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION) != 1) {
fprintf(stderr, "设置最小TLS版本失败\n");
SSL_CTX_free(ctx);
return NULL;
}
if (SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION) != 1) {
fprintf(stderr, "设置最大TLS版本失败\n");
SSL_CTX_free(ctx);
return NULL;
}
printf("配置为TLS 1.2/1.3自动模式\n");
}
// 加载客户端证书
printf("加载客户端证书: %s\n", cert_file);
if (SSL_CTX_use_certificate_file(ctx, cert_file, SSL_FILETYPE_PEM) != 1) {
fprintf(stderr, "加载客户端证书失败: %s\n", cert_file);
ERR_print_errors_fp(stderr);
SSL_CTX_free(ctx);
return NULL;
}
printf("客户端证书加载成功\n");
// 加载客户端私钥
printf("加载客户端私钥: %s\n", key_file);
if (SSL_CTX_use_PrivateKey_file(ctx, key_file, SSL_FILETYPE_PEM) != 1) {
fprintf(stderr, "加载客户端私钥失败: %s\n", key_file);
ERR_print_errors_fp(stderr);
SSL_CTX_free(ctx);
return NULL;
}
printf("客户端私钥加载成功\n");
// 验证私钥与证书匹配
printf("验证私钥与证书匹配...\n");
if (SSL_CTX_check_private_key(ctx) != 1) {
fprintf(stderr, "私钥与证书不匹配\n");
ERR_print_errors_fp(stderr);
SSL_CTX_free(ctx);
return NULL;
}
printf("私钥与证书匹配验证成功\n");
// 加载CA证书用于验证服务器证书
if (ca_file && SSL_CTX_load_verify_locations(ctx, ca_file, NULL) != 1) {
fprintf(stderr, "加载CA证书失败: %s\n", ca_file);
SSL_CTX_free(ctx);
return NULL;
}
// 设置验证模式
if (verify_peer) {
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
} else {
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
printf("警告: 已禁用服务器证书验证\n");
}
// 设置验证深度
SSL_CTX_set_verify_depth(ctx, 4);
return ctx;
}
/**
* @brief 创建TCP连接
* @param hostname 服务器主机名
* @param port 服务器端口
* @return int 套接字文件描述符,失败返回-1
*/
int create_tcp_connection(const char* hostname, int port) {
int sockfd;
struct sockaddr_in server_addr;
int opt = 1;
// 创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("创建套接字失败");
return -1;
}
// 设置套接字选项避免RST包问题
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("设置SO_REUSEADDR失败");
close(sockfd);
return -1;
}
// 设置TCP_NODELAY选项减少延迟
if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)) < 0) {
perror("设置TCP_NODELAY失败");
close(sockfd);
return -1;
}
// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
// 解析主机名
if (inet_pton(AF_INET, hostname, &server_addr.sin_addr) <= 0) {
fprintf(stderr, "无效的IP地址: %s\n", hostname);
close(sockfd);
return -1;
}
// 连接到服务器
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("连接服务器失败");
close(sockfd);
return -1;
}
printf("成功连接到服务器 %s:%d\n", hostname, port);
return sockfd;
}
/**
* @brief 分析系统调用错误的详细信息
* @param ssl SSL连接对象
* @param ret SSL函数返回值
* @return void
*/
void analyze_syscall_error(SSL* ssl, int ret) {
int sockfd = SSL_get_fd(ssl);
int error = 0;
socklen_t len = sizeof(error);
// 获取套接字错误
if (sockfd > 0 && getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) == 0) {
if (error != 0) {
fprintf(stderr, "套接字错误代码: %d (%s)\n", error, strerror(error));
// 详细分析套接字错误
switch (error) {
case ECONNREFUSED:
fprintf(stderr, " -> 连接被拒绝,服务器可能未运行或端口错误\n");
break;
case ETIMEDOUT:
fprintf(stderr, " -> 连接超时,网络可能有问题或服务器响应慢\n");
break;
case EHOSTUNREACH:
fprintf(stderr, " -> 主机不可达,网络路由问题\n");
break;
case ENETUNREACH:
fprintf(stderr, " -> 网络不可达,网络连接问题\n");
break;
case ECONNRESET:
fprintf(stderr, " -> 连接被重置,服务器主动断开连接\n");
break;
case EPIPE:
fprintf(stderr, " -> 管道破裂,连接意外断开\n");
break;
case EINTR:
fprintf(stderr, " -> 系统调用被中断\n");
break;
case EAGAIN:
fprintf(stderr, " -> 资源暂时不可用,可能需要重试\n");
break;
#ifdef EWOULDBLOCK
#if EAGAIN != EWOULDBLOCK
case EWOULDBLOCK:
fprintf(stderr, " -> 资源暂时不可用,可能需要重试\n");
break;
#endif
#endif
default:
fprintf(stderr, " -> 其他系统错误\n");
break;
}
}
}
// 检查errno
if (errno != 0) {
fprintf(stderr, "系统errno: %d (%s)\n", errno, strerror(errno));
}
// 检查SSL内部错误队列
unsigned long ssl_err;
while ((ssl_err = ERR_get_error()) != 0) {
char err_buf[256];
ERR_error_string_n(ssl_err, err_buf, sizeof(err_buf));
fprintf(stderr, "SSL内部错误: %s\n", err_buf);
}
}
/**
* @brief 执行TLS握手带重试机制
* @param ssl SSL连接对象
* @param max_retries 最大重试次数
* @param base_delay 基础延迟时间(秒)
* @return int 成功返回1失败返回0
*/
int perform_tls_handshake_with_retry(SSL* ssl, int max_retries, int base_delay) {
int ret;
time_t start_time, end_time, step_time;
double handshake_time, step_duration;
int retry_count = 0;
int sockfd = SSL_get_fd(ssl);
printf("开始TLS握手最大重试次数: %d...\n", max_retries);
start_time = time(NULL);
// 设置握手超时
if (sockfd > 0) {
struct timeval timeout;
timeout.tv_sec = 30; // 30秒超时
timeout.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
printf("设置握手超时: 30秒\n");
}
while (retry_count <= max_retries) {
if (retry_count > 0) {
int delay = base_delay * (1 << (retry_count - 1)); // 指数退避
printf("第 %d 次重试,等待 %d 秒...\n", retry_count, delay);
sleep(delay);
}
step_time = time(NULL);
printf("正在执行SSL_connect()... (尝试 %d/%d)\n", retry_count + 1, max_retries + 1);
// 执行SSL握手
ret = SSL_connect(ssl);
end_time = time(NULL);
handshake_time = difftime(end_time, start_time);
step_duration = difftime(end_time, step_time);
printf("SSL_connect() 耗时: %.2f 秒\n", step_duration);
if (ret == 1) {
// 握手成功
printf("TLS握手成功\n");
printf("总握手耗时: %.2f 秒\n", handshake_time);
printf("重试次数: %d\n", retry_count);
printf("协议版本: %s\n", SSL_get_version(ssl));
printf("密码套件: %s\n", SSL_get_cipher(ssl));
// 验证服务器证书
X509* cert = SSL_get_peer_certificate(ssl);
if (cert) {
printf("服务器证书验证成功\n");
X509_free(cert);
} else {
printf("警告: 无法获取服务器证书\n");
}
return 1;
}
// 握手失败,分析错误
int err = SSL_get_error(ssl, ret);
fprintf(stderr, "TLS握手失败错误代码: %d (尝试 %d/%d)\n", err, retry_count + 1, max_retries + 1);
// 详细的错误诊断
switch (err) {
case SSL_ERROR_SSL:
fprintf(stderr, "SSL协议错误可能是证书验证失败\n");
ERR_print_errors_fp(stderr);
break;
case SSL_ERROR_SYSCALL:
fprintf(stderr, "系统调用错误,详细分析:\n");
analyze_syscall_error(ssl, ret);
// 根据系统错误判断是否值得重试
if (sockfd > 0) {
int error = 0;
socklen_t len = sizeof(error);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) == 0) {
switch (error) {
case ECONNREFUSED:
case EHOSTUNREACH:
case ENETUNREACH:
fprintf(stderr, "网络连接问题,建议检查服务器地址和端口\n");
break;
case ETIMEDOUT:
fprintf(stderr, "连接超时,可能是网络延迟或服务器负载高\n");
break;
case ECONNRESET:
case EPIPE:
fprintf(stderr, "连接被重置,可能是服务器问题\n");
break;
case EAGAIN:
fprintf(stderr, "资源暂时不可用,适合重试\n");
break;
#ifdef EWOULDBLOCK
#if EAGAIN != EWOULDBLOCK
case EWOULDBLOCK:
fprintf(stderr, "资源暂时不可用,适合重试\n");
break;
#endif
#endif
default:
fprintf(stderr, "其他系统错误\n");
break;
}
}
}
break;
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
fprintf(stderr, "需要更多数据,这通常是正常的握手过程\n");
// 对于WANT_READ/WANT_WRITE通常不需要重试这是正常的握手流程
if (retry_count == 0) {
fprintf(stderr, "这是正常的握手流程,不需要重试\n");
return 0;
}
break;
default:
fprintf(stderr, "未知SSL错误\n");
ERR_print_errors_fp(stderr);
break;
}
retry_count++;
// 如果不是最后一次尝试,提供重试建议
if (retry_count <= max_retries) {
fprintf(stderr, "\n重试建议:\n");
fprintf(stderr, "1. 检查网络连接\n");
fprintf(stderr, "2. 验证服务器地址和端口\n");
fprintf(stderr, "3. 检查防火墙设置\n");
fprintf(stderr, "4. 尝试使用 --no-verify 选项\n");
fprintf(stderr, "5. 检查证书文件是否正确\n");
}
}
// 所有重试都失败了
fprintf(stderr, "\n所有重试尝试都失败了\n");
fprintf(stderr, "最终解决建议:\n");
fprintf(stderr, "1. 使用 --no-verify 选项禁用证书验证\n");
fprintf(stderr, "2. 检查服务器证书是否有效\n");
fprintf(stderr, "3. 确保CA证书文件正确\n");
fprintf(stderr, "4. 检查系统时间是否正确\n");
fprintf(stderr, "5. 检查网络连接和防火墙设置\n");
fprintf(stderr, "6. 尝试使用不同的TLS版本\n");
return 0;
}
/**
* @brief 执行TLS握手保持向后兼容
* @param ssl SSL连接对象
* @return int 成功返回1失败返回0
*/
int perform_tls_handshake(SSL* ssl) {
// 使用默认重试参数最多重试3次基础延迟2秒
return perform_tls_handshake_with_retry(ssl, 3, 2);
}
/**
* @brief 发送测试数据
* @param ssl SSL连接对象
* @return int 成功返回1失败返回0
*/
int send_test_data(SSL* ssl) {
char test_data[TEST_DATA_SIZE];
int bytes_sent;
time_t start_time, end_time;
double send_time;
// 生成1KB测试数据
for (int i = 0; i < TEST_DATA_SIZE; i++) {
test_data[i] = 'A' + (i % 26);
}
printf("发送 %d 字节测试数据...\n", TEST_DATA_SIZE);
start_time = time(NULL);
// 发送数据
bytes_sent = SSL_write(ssl, test_data, TEST_DATA_SIZE);
end_time = time(NULL);
send_time = difftime(end_time, start_time);
printf("SSL_write() 耗时: %.2f 秒\n", send_time);
if (bytes_sent <= 0) {
int err = SSL_get_error(ssl, bytes_sent);
fprintf(stderr, "发送数据失败,错误代码: %d\n", err);
return 0;
}
printf("成功发送 %d 字节数据\n", bytes_sent);
return 1;
}
/**
* @brief 接收服务器响应
* @param ssl SSL连接对象
* @return int 成功返回1失败返回0
*/
int receive_response(SSL* ssl) {
char buffer[MAX_BUFFER_SIZE];
int bytes_received;
time_t start_time, end_time;
double receive_time;
int timeout_seconds = 30; // 30秒超时
printf("等待服务器响应(超时: %d秒...\n", timeout_seconds);
start_time = time(NULL);
// 设置socket超时
int sockfd = SSL_get_fd(ssl);
if (sockfd > 0) {
struct timeval timeout;
timeout.tv_sec = timeout_seconds;
timeout.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
}
// 接收数据
bytes_received = SSL_read(ssl, buffer, MAX_BUFFER_SIZE - 1);
end_time = time(NULL);
receive_time = difftime(end_time, start_time);
printf("SSL_read() 耗时: %.2f 秒\n", receive_time);
// 检查是否超时
if (receive_time >= timeout_seconds) {
printf("⚠ 警告: 服务器响应超时(%.2f秒)\n", receive_time);
}
if (bytes_received <= 0) {
int err = SSL_get_error(ssl, bytes_received);
fprintf(stderr, "接收数据失败,错误代码: %d\n", err);
return 0;
}
buffer[bytes_received] = '\0';
printf("接收到 %d 字节响应:\n", bytes_received);
printf("--- 响应内容 ---\n");
printf("%s\n", buffer);
printf("--- 响应结束 ---\n");
// 检查是否收到预期的HTTP响应
if (strstr(buffer, "HTTP/1.1 200 OK") && strstr(buffer, "Hello from TLS server!")) {
printf("✓ 收到预期的HTTP响应\n");
} else {
printf("⚠ 响应内容与预期不符\n");
}
return 1;
}
/**
* @brief 执行单次TLS测试
* @param hostname 服务器主机名
* @param port 服务器端口
* @param cert_file 客户端证书文件
* @param key_file 客户端私钥文件
* @param ca_file CA证书文件
* @param verify_peer 是否验证服务器证书
* @param tls_version TLS版本
* @param debug_level 调试级别
* @param max_retries 最大重试次数
* @param retry_delay 重试延迟
* @return int 成功返回1失败返回0
*/
int perform_tls_test(const char* hostname, int port, const char* cert_file,
const char* key_file, const char* ca_file, int verify_peer,
int tls_version, int debug_level, int max_retries, int retry_delay) {
int sockfd;
SSL* ssl;
SSL_CTX* local_ctx = NULL;
int success = 0;
// 创建TCP连接
sockfd = create_tcp_connection(hostname, port);
if (sockfd < 0) {
return 0;
}
// 创建本地SSL上下文如果提供了tls_version参数
if (tls_version > 0) {
printf("为TLS版本 %d 创建SSL上下文...\n", tls_version);
local_ctx = create_ssl_context(cert_file, key_file, ca_file, verify_peer, tls_version, debug_level);
if (!local_ctx) {
fprintf(stderr, "创建SSL上下文失败\n");
close(sockfd);
return 0;
}
printf("SSL上下文创建成功\n");
}
// 创建SSL连接
ssl = SSL_new(local_ctx ? local_ctx : ssl_ctx);
if (!ssl) {
fprintf(stderr, "创建SSL连接失败\n");
if (local_ctx) {
SSL_CTX_free(local_ctx);
}
close(sockfd);
return 0;
}
// 将套接字绑定到SSL连接
SSL_set_fd(ssl, sockfd);
// 执行TLS握手使用指定的重试参数
if (perform_tls_handshake_with_retry(ssl, max_retries, retry_delay)) {
// 发送测试数据
if (send_test_data(ssl)) {
// 接收响应
if (receive_response(ssl)) {
success = 1;
}
}
}
// 安全关闭SSL连接和套接字
if (ssl) {
// 检查SSL连接状态
int shutdown_state = SSL_get_shutdown(ssl);
printf("SSL关闭前状态: %d\n", shutdown_state);
if (shutdown_state == 0) {
// 连接仍然活跃,需要优雅关闭
printf("开始优雅关闭SSL连接...\n");
// 设置SSL关闭模式为双向关闭
SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
// 尝试优雅关闭SSL连接
int ret = SSL_shutdown(ssl);
printf("第一次SSL_shutdown返回: %d\n", ret);
if (ret == 0) {
// 需要再次调用SSL_shutdown完成双向关闭
printf("执行第二次SSL关闭...\n");
ret = SSL_shutdown(ssl);
printf("第二次SSL_shutdown返回: %d\n", ret);
}
// 等待一段时间确保关闭完成
usleep(100000); // 等待100ms
}
// 释放SSL对象
SSL_free(ssl);
}
// 关闭套接字
if (sockfd > 0) {
// 检查套接字状态
int error = 0;
socklen_t len = sizeof(error);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) == 0) {
if (error != 0) {
printf("套接字错误: %d\n", error);
}
}
// 设置套接字选项避免TIME_WAIT状态
int opt = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("设置SO_REUSEADDR失败");
}
// 设置LINGER选项确保数据发送完成
struct linger linger_opt;
linger_opt.l_onoff = 1;
linger_opt.l_linger = 5; // 等待5秒
if (setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(linger_opt)) < 0) {
perror("设置SO_LINGER失败");
}
// 关闭套接字
close(sockfd);
}
if (local_ctx) {
SSL_CTX_free(local_ctx);
}
return success;
}
/**
* @brief 执行TLS版本循环测试
* @param hostname 服务器主机名
* @param port 服务器端口
* @param cert_file 客户端证书文件
* @param key_file 客户端私钥文件
* @param ca_file CA证书文件
* @param verify_peer 是否验证服务器证书
* @param test_count 测试次数 (-1表示无限循环)
* @param interval 测试间隔
* @param continue_on_failure 失败时是否继续
* @param debug_level 调试级别
* @param max_retries 最大重试次数
* @param retry_delay 重试延迟
* @return int 成功返回1失败返回0
*/
int perform_tls_version_test(const char* hostname, int port, const char* cert_file,
const char* key_file, const char* ca_file, int verify_peer,
int test_count, int interval, int continue_on_failure,
int debug_level, int max_retries, int retry_delay) {
int test_num = 0;
int success_count = 0;
int total_tests = 0;
time_t start_time, end_time;
printf("=== TLS版本循环测试开始 ===\n");
printf("服务器: %s:%d\n", hostname, port);
printf("测试模式: TLS 1.2 -> TLS 1.3 -> 重复\n");
printf("测试次数: %s\n", test_count == -1 ? "无限循环" : "有限次数");
printf("测试间隔: %d 秒\n", interval);
printf("========================\n\n");
start_time = time(NULL);
while (test_count == -1 || test_num < test_count) {
test_num++;
total_tests++;
// 测试TLS 1.2
printf("\n--- 第 %d 次测试 - TLS 1.2 ---\n", test_num);
if (perform_tls_test(hostname, port, cert_file, key_file, ca_file, verify_peer, 1, debug_level, max_retries, retry_delay)) {
printf("✓ TLS 1.2 测试成功\n");
success_count++;
} else {
printf("✗ TLS 1.2 测试失败\n");
if (!continue_on_failure) {
printf("TLS 1.2 测试失败,退出测试循环\n");
break; // 失败时退出循环
} else {
printf("TLS 1.2 测试失败,继续测试\n");
}
}
// 等待间隔
if (interval > 0) {
sleep(interval);
}
// 测试TLS 1.3
printf("\n--- 第 %d 次测试 - TLS 1.3 ---\n", test_num);
if (perform_tls_test(hostname, port, cert_file, key_file, ca_file, verify_peer, 2, debug_level, max_retries, retry_delay)) {
printf("✓ TLS 1.3 测试成功\n");
success_count++;
} else {
printf("✗ TLS 1.3 测试失败\n");
if (!continue_on_failure) {
printf("TLS 1.3 测试失败,退出测试循环\n");
break; // 失败时退出循环
} else {
printf("TLS 1.3 测试失败,继续测试\n");
}
}
// 如果不是最后一次测试,等待指定间隔
if (test_count == -1 || test_num < test_count) {
if (interval > 0) {
printf("等待 %d 秒后进行下次测试...\n", interval);
sleep(interval);
}
}
}
end_time = time(NULL);
// 打印测试统计
printf("\n=== 测试统计 ===\n");
printf("总测试次数: %d (TLS 1.2: %d次, TLS 1.3: %d次)\n", total_tests * 2, total_tests, total_tests);
printf("成功次数: %d\n", success_count);
printf("失败次数: %d\n", total_tests * 2 - success_count);
printf("成功率: %.2f%%\n", total_tests > 0 ? (double)success_count / (total_tests * 2) * 100 : 0);
printf("总耗时: %ld 秒\n", end_time - start_time);
printf("===============\n");
return success_count > 0 ? 1 : 0;
}
/**
* @brief 打印使用说明
*/
void print_usage(const char* program_name) {
printf("用法: %s [选项]\n", program_name);
printf("选项:\n");
printf(" -h <hostname> 服务器主机名或IP地址 (默认: 127.0.0.1)\n");
printf(" -p <port> 服务器端口 (默认: 443)\n");
printf(" -c <cert_file> 客户端证书文件 (必需)\n");
printf(" -k <key_file> 客户端私钥文件 (必需)\n");
printf(" -a <ca_file> CA证书文件 (可选)\n");
printf(" -n <count> 测试次数 (默认: 无限循环)\n");
printf(" -i <interval> 测试间隔秒数 (默认: 1)\n");
printf(" -v 详细输出\n");
printf(" -x 禁用服务器证书验证\n");
printf(" -t <version> TLS版本 (1=TLS1.2, 2=TLS1.3, 3=循环测试1.2和1.3)\n");
printf(" -f 失败时继续测试(默认:失败时退出)\n");
printf(" -d <level> OpenSSL调试级别 (0=无, 1=基本, 2=详细, 3=完整)\n");
printf(" -r <retries> 握手重试次数 (默认: 3)\n");
printf(" -w <delay> 重试基础延迟秒数 (默认: 2)\n");
printf(" --no-verify 禁用服务器证书验证\n");
printf(" --help 显示此帮助信息\n");
printf("\n示例:\n");
printf(" %s -h 192.168.1.100 -p 8443 -c client.crt -k client.key\n", program_name);
printf(" %s -h example.com -p 443 -c client.crt -k client.key -a ca.crt -n 10\n", program_name);
printf(" %s -h server.com -c client.crt -k client.key -r 5 -w 3\n", program_name);
}
int main(int argc, char *argv[]) {
char hostname[256] = "127.0.0.1";
int port = 443;
char cert_file[256] = "";
char key_file[256] = "";
char ca_file[256] = "";
int test_count = -1; // -1表示无限循环
int interval = 1;
int verbose = 0;
int no_verify = 0; // 是否禁用服务器证书验证
int tls_version = 3; // 默认循环测试TLS 1.2和1.3
int continue_on_failure = 0; // 失败时是否继续测试
int debug_level = 0; // OpenSSL调试级别
int max_retries = 3; // 默认重试次数
int retry_delay = 2; // 默认重试延迟
int opt;
int test_num = 0;
int success_count = 0;
int total_tests = 0;
time_t start_time, end_time;
// 预处理长选项,将它们转换为短选项
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--no-verify") == 0) {
no_verify = 1;
// 移除这个参数避免getopt处理
for (int j = i; j < argc - 1; j++) {
argv[j] = argv[j + 1];
}
argc--;
i--;
} else if (strcmp(argv[i], "--help") == 0) {
print_usage(argv[0]);
return 0;
}
}
// 解析命令行参数
while ((opt = getopt(argc, argv, "h:p:c:k:a:n:i:vxt:fd:r:w:")) != -1) {
switch (opt) {
case 'h':
strncpy(hostname, optarg, sizeof(hostname) - 1);
break;
case 'p':
port = atoi(optarg);
break;
case 'c':
strncpy(cert_file, optarg, sizeof(cert_file) - 1);
break;
case 'k':
strncpy(key_file, optarg, sizeof(key_file) - 1);
break;
case 'a':
strncpy(ca_file, optarg, sizeof(ca_file) - 1);
break;
case 'n':
test_count = atoi(optarg);
break;
case 'i':
interval = atoi(optarg);
break;
case 'v':
verbose = 1;
break;
case 'x':
no_verify = 1;
break;
case 't':
tls_version = atoi(optarg);
if (tls_version < 1 || tls_version > 3) {
fprintf(stderr, "错误: TLS版本必须是1、2或3\n");
print_usage(argv[0]);
return 1;
}
break;
case 'f':
continue_on_failure = 1;
break;
case 'd':
debug_level = atoi(optarg);
if (debug_level < 0 || debug_level > 3) {
fprintf(stderr, "错误: 调试级别必须是0-3\n");
print_usage(argv[0]);
return 1;
}
break;
case 'r':
max_retries = atoi(optarg);
if (max_retries < 0 || max_retries > 10) {
fprintf(stderr, "错误: 重试次数必须是0-10\n");
print_usage(argv[0]);
return 1;
}
break;
case 'w':
retry_delay = atoi(optarg);
if (retry_delay < 1 || retry_delay > 60) {
fprintf(stderr, "错误: 重试延迟必须是1-60秒\n");
print_usage(argv[0]);
return 1;
}
break;
default:
print_usage(argv[0]);
return 1;
}
}
// 检查必需参数
if (strlen(cert_file) == 0 || strlen(key_file) == 0) {
fprintf(stderr, "错误: 必须指定客户端证书和私钥文件\n");
print_usage(argv[0]);
return 1;
}
// 初始化OpenSSL
init_openssl(debug_level);
// 根据TLS版本选择测试模式
if (tls_version == 3) {
// 循环测试TLS 1.2和1.3
return perform_tls_version_test(hostname, port, cert_file, key_file,
ca_file[0] ? ca_file : NULL, !no_verify,
test_count, interval, continue_on_failure, debug_level, max_retries, retry_delay);
} else {
// 单版本测试
// 创建SSL上下文
ssl_ctx = create_ssl_context(cert_file, key_file, ca_file[0] ? ca_file : NULL, !no_verify, tls_version, debug_level);
if (!ssl_ctx) {
fprintf(stderr, "创建SSL上下文失败\n");
cleanup_openssl();
return 1;
}
}
printf("=== TLS客户端测试程序 ===\n");
printf("服务器: %s:%d\n", hostname, port);
printf("客户端证书: %s\n", cert_file);
printf("客户端私钥: %s\n", key_file);
if (strlen(ca_file) > 0) {
printf("CA证书: %s\n", ca_file);
}
printf("测试次数: %s\n", test_count == -1 ? "无限循环" : "有限次数");
if (test_count != -1) {
printf("计划测试: %d 次\n", test_count);
}
printf("测试间隔: %d 秒\n", interval);
printf("========================\n\n");
start_time = time(NULL);
// 开始测试循环
while (test_count == -1 || test_num < test_count) {
test_num++;
total_tests++;
printf("\n--- 第 %d 次测试 ---\n", test_num);
if (verbose) {
printf("开始时间: %s", ctime(&start_time));
}
// 执行TLS测试
if (perform_tls_test(hostname, port, cert_file, key_file, ca_file, !no_verify, tls_version, debug_level, max_retries, retry_delay)) {
printf("✓ 第 %d 次测试成功\n", test_num);
success_count++;
} else {
printf("✗ 第 %d 次测试失败\n", test_num);
if (!continue_on_failure) {
printf("测试失败,退出测试循环\n");
break; // 失败时退出循环
} else {
printf("测试失败,继续测试\n");
}
}
// 如果不是最后一次测试,等待指定间隔
if (test_count == -1 || test_num < test_count) {
if (interval > 0) {
printf("等待 %d 秒后进行下次测试...\n", interval);
sleep(interval);
}
}
}
end_time = time(NULL);
// 打印测试统计
printf("\n=== 测试统计 ===\n");
printf("总测试次数: %d\n", total_tests);
printf("成功次数: %d\n", success_count);
printf("失败次数: %d\n", total_tests - success_count);
printf("成功率: %.2f%%\n", total_tests > 0 ? (double)success_count / total_tests * 100 : 0);
printf("总耗时: %ld 秒\n", end_time - start_time);
printf("===============\n");
// 清理资源
cleanup_openssl();
return 0;
}