feat(tls): 添加 TLS/HTTPS 支持
- 新增 tls.c/tls.h,使用 OpenSSL Memory BIO 与 coco 协程 I/O 集成 - 支持 TLS 1.2+,通过命令行 --cert/--key 或配置文件启用 - 所有静态文件、目录浏览、POST/文件上传功能均已支持 HTTPS - 集成测试新增 TLS 握手 + HTTPS 响应验证 - 单元测试 127 项通过,集成测试 39 项通过
This commit is contained in:
parent
8f9d420447
commit
7747ec921f
12
Makefile
12
Makefile
@ -7,7 +7,7 @@ COCO_LIB ?= $(COCO_DIR)/build
|
||||
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -O2 -std=c11 -D_GNU_SOURCE -I$(COCO_INCLUDE)
|
||||
LDFLAGS = -L$(COCO_LIB) -lcoco -lpthread -lm -luring -lz -lbrotlienc
|
||||
LDFLAGS = -L$(COCO_LIB) -lcoco -lpthread -lm -luring -lz -lbrotlienc -lssl -lcrypto
|
||||
|
||||
# 调试模式
|
||||
DEBUG ?= 0
|
||||
@ -20,7 +20,7 @@ PREFIX ?= /usr/local
|
||||
BINDIR = $(PREFIX)/bin
|
||||
|
||||
# 源文件
|
||||
SRCS = main.c server.c http.c static.c log.c config.c multipart.c
|
||||
SRCS = main.c server.c http.c static.c log.c config.c multipart.c tls.c
|
||||
OBJS = $(SRCS:.c=.o)
|
||||
TARGET = cocoon
|
||||
|
||||
@ -78,8 +78,8 @@ unit-test: $(UNIT_TEST_BINS)
|
||||
fi
|
||||
|
||||
# 单元测试编译规则
|
||||
$(UNIT_TEST_DIR)/test_server: $(UNIT_TEST_DIR)/test_server.c server.c http.c static.c log.c config.c multipart.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_server.c server.c http.c static.c log.c config.c multipart.c $(UNITY_SRC) $(LDFLAGS)
|
||||
$(UNIT_TEST_DIR)/test_server: $(UNIT_TEST_DIR)/test_server.c server.c http.c static.c log.c config.c multipart.c tls.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_server.c server.c http.c static.c log.c config.c multipart.c tls.c $(UNITY_SRC) $(LDFLAGS)
|
||||
|
||||
$(UNIT_TEST_DIR)/test_multipart: $(UNIT_TEST_DIR)/test_multipart.c multipart.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_multipart.c multipart.c $(UNITY_SRC) -lm
|
||||
@ -87,8 +87,8 @@ $(UNIT_TEST_DIR)/test_multipart: $(UNIT_TEST_DIR)/test_multipart.c multipart.c $
|
||||
$(UNIT_TEST_DIR)/test_http: $(UNIT_TEST_DIR)/test_http.c http.c log.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_http.c http.c log.c $(UNITY_SRC) -lm
|
||||
|
||||
$(UNIT_TEST_DIR)/test_static: $(UNIT_TEST_DIR)/test_static.c http.c log.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_static.c http.c log.c $(UNITY_SRC) -lm -lz -lbrotlienc
|
||||
$(UNIT_TEST_DIR)/test_static: $(UNIT_TEST_DIR)/test_static.c http.c log.c tls.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_static.c http.c log.c tls.c $(UNITY_SRC) $(LDFLAGS)
|
||||
|
||||
$(UNIT_TEST_DIR)/test_log: $(UNIT_TEST_DIR)/test_log.c log.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_log.c log.c $(UNITY_SRC) -lm
|
||||
|
||||
3
cocoon.h
3
cocoon.h
@ -38,6 +38,9 @@ typedef struct cocoon_config {
|
||||
log_level_t log_level; /**< 日志级别 */
|
||||
bool gzip_enabled; /**< 是否启用 gzip 压缩(默认 true) */
|
||||
bool brotli_enabled; /**< 是否启用 brotli 压缩(默认 true) */
|
||||
bool tls_enabled; /**< 是否启用 TLS(由 cert/key 自动推断) */
|
||||
const char *tls_cert; /**< TLS 证书路径 */
|
||||
const char *tls_key; /**< TLS 私钥路径 */
|
||||
} cocoon_config_t;
|
||||
|
||||
/* === 服务器生命周期 API === */
|
||||
|
||||
27
config.c
27
config.c
@ -274,6 +274,21 @@ bool config_load_from_file(const char *path, cocoon_config_t *config) {
|
||||
} else if (strcmp(key_str, "brotli_enabled") == 0) {
|
||||
if (val.type == TOKEN_TRUE) config->brotli_enabled = true;
|
||||
else if (val.type == TOKEN_FALSE) config->brotli_enabled = false;
|
||||
} else if (strcmp(key_str, "tls_cert") == 0 && val.type == TOKEN_STRING) {
|
||||
char *v = token_str_dup(&val);
|
||||
if (v) {
|
||||
free((void *)config->tls_cert);
|
||||
config->tls_cert = v;
|
||||
}
|
||||
} else if (strcmp(key_str, "tls_key") == 0 && val.type == TOKEN_STRING) {
|
||||
char *v = token_str_dup(&val);
|
||||
if (v) {
|
||||
free((void *)config->tls_key);
|
||||
config->tls_key = v;
|
||||
}
|
||||
} else if (strcmp(key_str, "tls_enabled") == 0) {
|
||||
if (val.type == TOKEN_TRUE) config->tls_enabled = true;
|
||||
else if (val.type == TOKEN_FALSE) config->tls_enabled = false;
|
||||
}
|
||||
/* 其他字段:忽略(未来扩展预留) */
|
||||
|
||||
@ -296,7 +311,8 @@ bool config_load_from_file(const char *path, cocoon_config_t *config) {
|
||||
void config_merge(cocoon_config_t *base, const cocoon_config_t *cmdline,
|
||||
bool has_root_dir, bool has_port, bool has_workers,
|
||||
bool has_max_conn, bool has_timeout, bool has_log_level,
|
||||
bool has_gzip_enabled, bool has_brotli_enabled) {
|
||||
bool has_gzip_enabled, bool has_brotli_enabled,
|
||||
bool has_tls_cert, bool has_tls_key, bool has_tls_enabled) {
|
||||
if (!base || !cmdline) return;
|
||||
|
||||
/* 命令行显式指定的值覆盖配置文件 */
|
||||
@ -311,6 +327,15 @@ void config_merge(cocoon_config_t *base, const cocoon_config_t *cmdline,
|
||||
if (has_log_level) base->log_level = cmdline->log_level;
|
||||
if (has_gzip_enabled) base->gzip_enabled = cmdline->gzip_enabled;
|
||||
if (has_brotli_enabled) base->brotli_enabled = cmdline->brotli_enabled;
|
||||
if (has_tls_cert && cmdline->tls_cert) {
|
||||
free((void *)base->tls_cert);
|
||||
base->tls_cert = strdup(cmdline->tls_cert);
|
||||
}
|
||||
if (has_tls_key && cmdline->tls_key) {
|
||||
free((void *)base->tls_key);
|
||||
base->tls_key = strdup(cmdline->tls_key);
|
||||
}
|
||||
if (has_tls_enabled) base->tls_enabled = cmdline->tls_enabled;
|
||||
/* threaded 是 flag 参数,命令行指定了就用命令行的 */
|
||||
if (cmdline->threaded) base->threaded = true;
|
||||
}
|
||||
|
||||
3
config.h
3
config.h
@ -43,6 +43,7 @@ bool config_load_from_file(const char *path, cocoon_config_t *config);
|
||||
void config_merge(cocoon_config_t *base, const cocoon_config_t *cmdline,
|
||||
bool has_root_dir, bool has_port, bool has_workers,
|
||||
bool has_max_conn, bool has_timeout, bool has_log_level,
|
||||
bool has_gzip_enabled, bool has_brotli_enabled);
|
||||
bool has_gzip_enabled, bool has_brotli_enabled,
|
||||
bool has_tls_cert, bool has_tls_key, bool has_tls_enabled);
|
||||
|
||||
#endif /* COCOON_CONFIG_H */
|
||||
|
||||
34
main.c
34
main.c
@ -51,6 +51,9 @@ static void print_usage(const char *prog) {
|
||||
printf(" -o <ms> 连接空闲超时毫秒(默认 30000)\n");
|
||||
printf(" -l <level> 日志级别: error, warn, info, debug(默认 info)\n");
|
||||
printf(" -v 详细日志输出(等同于 -l debug)\n");
|
||||
printf(" --cert <path> TLS 证书路径(启用 HTTPS)\n");
|
||||
printf(" --key <path> TLS 私钥路径\n");
|
||||
printf(" --tls 显式启用 TLS(需同时指定 --cert 和 --key)\n");
|
||||
printf(" --no-gzip 禁用 gzip 压缩\n");
|
||||
printf(" --no-brotli 禁用 brotli 压缩\n");
|
||||
printf(" -h 显示此帮助\n");
|
||||
@ -82,6 +85,10 @@ static bool parse_args(int argc, char *argv[], cocoon_config_t *config) {
|
||||
config->gzip_enabled = true;
|
||||
config->brotli_enabled = true;
|
||||
|
||||
config->tls_cert = NULL;
|
||||
config->tls_key = NULL;
|
||||
config->tls_enabled = false;
|
||||
|
||||
bool has_root_dir = false;
|
||||
bool has_port = false;
|
||||
bool has_workers = false;
|
||||
@ -90,6 +97,9 @@ static bool parse_args(int argc, char *argv[], cocoon_config_t *config) {
|
||||
bool has_log_level = false;
|
||||
bool has_gzip_enabled = false;
|
||||
bool has_brotli_enabled = false;
|
||||
bool has_tls_cert = false;
|
||||
bool has_tls_key = false;
|
||||
bool has_tls_enabled = false;
|
||||
const char *config_file = NULL;
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
@ -139,6 +149,17 @@ static bool parse_args(int argc, char *argv[], cocoon_config_t *config) {
|
||||
} else if (strcmp(argv[i], "--no-brotli") == 0) {
|
||||
config->brotli_enabled = false;
|
||||
has_brotli_enabled = true;
|
||||
} else if (strcmp(argv[i], "--cert") == 0) {
|
||||
if (++i >= argc) return false;
|
||||
config->tls_cert = strdup(argv[i]);
|
||||
has_tls_cert = true;
|
||||
} else if (strcmp(argv[i], "--key") == 0) {
|
||||
if (++i >= argc) return false;
|
||||
config->tls_key = strdup(argv[i]);
|
||||
has_tls_key = true;
|
||||
} else if (strcmp(argv[i], "--tls") == 0) {
|
||||
config->tls_enabled = true;
|
||||
has_tls_enabled = true;
|
||||
} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
||||
print_usage(argv[0]);
|
||||
exit(0);
|
||||
@ -156,7 +177,9 @@ static bool parse_args(int argc, char *argv[], cocoon_config_t *config) {
|
||||
}
|
||||
/* 用命令行参数覆盖配置文件 */
|
||||
config_merge(config, config, has_root_dir, has_port, has_workers,
|
||||
has_max_conn, has_timeout, has_log_level, has_gzip_enabled, has_brotli_enabled);
|
||||
has_max_conn, has_timeout, has_log_level,
|
||||
has_gzip_enabled, has_brotli_enabled,
|
||||
has_tls_cert, has_tls_key, has_tls_enabled);
|
||||
}
|
||||
|
||||
if (!config->root_dir) {
|
||||
@ -205,11 +228,10 @@ int main(int argc, char *argv[]) {
|
||||
server_destroy(g_ctx);
|
||||
g_ctx = NULL;
|
||||
|
||||
/* 释放配置文件分配的 root_dir */
|
||||
if (config.root_dir) {
|
||||
free((void *)config.root_dir);
|
||||
config.root_dir = NULL;
|
||||
}
|
||||
/* 释放配置文件分配的内存 */
|
||||
if (config.root_dir) free((void *)config.root_dir);
|
||||
if (config.tls_cert) free((void *)config.tls_cert);
|
||||
if (config.tls_key) free((void *)config.tls_key);
|
||||
|
||||
return ret == COCOON_OK ? 0 : 1;
|
||||
}
|
||||
|
||||
46
server.c
46
server.c
@ -21,6 +21,7 @@
|
||||
#include "cocoon.h"
|
||||
#include "log.h"
|
||||
#include "multipart.h"
|
||||
#include "tls.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -88,6 +89,7 @@ static void set_nonblocking(int fd) {
|
||||
*/
|
||||
static void close_connection(connection_t *conn) {
|
||||
if (conn && conn->fd >= 0) {
|
||||
tls_close(conn->fd);
|
||||
close(conn->fd);
|
||||
conn->fd = -1;
|
||||
conn->closed = true;
|
||||
@ -112,8 +114,9 @@ static ssize_t conn_read(connection_t *conn) {
|
||||
|
||||
ssize_t n;
|
||||
|
||||
/* 尝试使用 coco 的异步 I/O */
|
||||
if (coco_sched_get_current() != NULL) {
|
||||
if (tls_has_connection(conn->fd)) {
|
||||
n = tls_read(conn->fd, conn->buf + conn->buf_len, space);
|
||||
} else if (coco_sched_get_current() != NULL) {
|
||||
n = coco_read(conn->fd, conn->buf + conn->buf_len, space);
|
||||
} else {
|
||||
/* 无调度器时直接 read */
|
||||
@ -164,7 +167,9 @@ static int conn_read_body(connection_t *conn, http_request_t *req, size_t need)
|
||||
/* 从 socket 读取剩余数据 */
|
||||
while (got < need) {
|
||||
ssize_t n;
|
||||
if (coco_sched_get_current() != NULL) {
|
||||
if (tls_has_connection(conn->fd)) {
|
||||
n = tls_read(conn->fd, req->body + got, need - got);
|
||||
} else if (coco_sched_get_current() != NULL) {
|
||||
n = coco_read(conn->fd, req->body + got, need - got);
|
||||
} else {
|
||||
n = read(conn->fd, req->body + got, need - got);
|
||||
@ -600,9 +605,17 @@ static void accept_loop(void *arg) {
|
||||
const char *resp = "HTTP/1.1 503 Service Unavailable\r\n"
|
||||
"Content-Length: 0\r\n"
|
||||
"Connection: close\r\n\r\n";
|
||||
ssize_t wr = write(client_fd, resp, strlen(resp));
|
||||
(void)wr;
|
||||
close(client_fd);
|
||||
if (tls_has_context()) {
|
||||
if (tls_accept(client_fd) == 0) {
|
||||
tls_write(client_fd, resp, strlen(resp));
|
||||
tls_close(client_fd);
|
||||
}
|
||||
close(client_fd);
|
||||
} else {
|
||||
ssize_t wr = write(client_fd, resp, strlen(resp));
|
||||
(void)wr;
|
||||
close(client_fd);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -611,6 +624,15 @@ static void accept_loop(void *arg) {
|
||||
int opt = 1;
|
||||
setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
|
||||
|
||||
/* TLS 握手(如果启用) */
|
||||
if (tls_has_context()) {
|
||||
if (tls_accept(client_fd) != 0) {
|
||||
log_warn("TLS 握手失败 fd=%d,关闭连接", client_fd);
|
||||
close(client_fd);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* 创建连接上下文 */
|
||||
connection_t *conn = (connection_t *)calloc(1, sizeof(connection_t));
|
||||
if (!conn) {
|
||||
@ -696,6 +718,16 @@ server_context_t *server_create(const cocoon_config_t *config) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 初始化 TLS 上下文(如果配置了证书) */
|
||||
if (ctx->config.tls_cert && ctx->config.tls_key) {
|
||||
if (tls_create_context(ctx->config.tls_cert, ctx->config.tls_key) != 0) {
|
||||
log_error("TLS 上下文初始化失败");
|
||||
close(ctx->listen_fd);
|
||||
free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* 非阻塞模式 */
|
||||
set_nonblocking(ctx->listen_fd);
|
||||
|
||||
@ -765,6 +797,8 @@ void server_stop(server_context_t *ctx) {
|
||||
void server_destroy(server_context_t *ctx) {
|
||||
if (!ctx) return;
|
||||
|
||||
tls_destroy_context();
|
||||
|
||||
if (ctx->listen_fd >= 0) {
|
||||
close(ctx->listen_fd);
|
||||
ctx->listen_fd = -1;
|
||||
|
||||
28
static.c
28
static.c
@ -10,6 +10,7 @@
|
||||
|
||||
#include "static.h"
|
||||
#include "cocoon.h"
|
||||
#include "tls.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -239,6 +240,10 @@ static bool safe_path_join(char *dst, size_t dst_size,
|
||||
* @return 0 成功,-1 失败
|
||||
*/
|
||||
int send_all(int fd, const char *buf, size_t len) {
|
||||
if (tls_has_connection(fd)) {
|
||||
return tls_write(fd, buf, len) == (ssize_t)len ? 0 : -1;
|
||||
}
|
||||
|
||||
size_t sent = 0;
|
||||
while (sent < len) {
|
||||
ssize_t n = write(fd, buf + sent, len - sent);
|
||||
@ -470,10 +475,25 @@ int static_serve_file(int fd, const http_request_t *req, const char *root_dir, b
|
||||
return COCOON_OK;
|
||||
}
|
||||
|
||||
if (use_gzip || use_brotli) {
|
||||
/* 发送压缩后的数据 */
|
||||
send_all(fd, compress_buf, (size_t)compress_len);
|
||||
free(compress_buf);
|
||||
if (use_gzip || use_brotli || tls_has_connection(fd)) {
|
||||
/* 发送压缩后的数据,或 TLS 模式下的文件内容 */
|
||||
if (use_gzip || use_brotli) {
|
||||
send_all(fd, compress_buf, (size_t)compress_len);
|
||||
free(compress_buf);
|
||||
} else {
|
||||
/* 定位到起始位置(文件可能已被压缩读取提前读至末尾) */
|
||||
lseek(file_fd, send_start, SEEK_SET);
|
||||
/* TLS 模式:不能使用 sendfile,需读取文件后发送 */
|
||||
char file_buf[65536];
|
||||
ssize_t remaining = send_length;
|
||||
while (remaining > 0) {
|
||||
size_t to_read = (size_t)remaining < sizeof(file_buf) ? (size_t)remaining : sizeof(file_buf);
|
||||
ssize_t n = read(file_fd, file_buf, to_read);
|
||||
if (n <= 0) break;
|
||||
if (send_all(fd, file_buf, (size_t)n) != 0) break;
|
||||
remaining -= n;
|
||||
}
|
||||
}
|
||||
close(file_fd);
|
||||
} else {
|
||||
/* 定位到起始位置 */
|
||||
|
||||
@ -18,7 +18,9 @@ TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$TMPDIR"; kill_server' EXIT
|
||||
|
||||
kill_server() {
|
||||
pkill -f "$SERVER.*$HOST" 2>/dev/null || true
|
||||
pkill -f "cocoon.*-p 9999" 2>/dev/null || true
|
||||
pkill -f "cocoon.*--cert" 2>/dev/null || true
|
||||
sleep 0.5
|
||||
}
|
||||
|
||||
# 启动服务器
|
||||
@ -145,7 +147,7 @@ assert_body_contains() {
|
||||
local expect="$2"
|
||||
local desc="${3:-$url}"
|
||||
local body
|
||||
body=$(curl -s "$url")
|
||||
body=$(curl -s -k "$url")
|
||||
if echo "$body" | grep -q "$expect"; then
|
||||
echo " ✓ $desc — 响应体包含 '$expect'"
|
||||
pass
|
||||
@ -411,6 +413,45 @@ echo ""
|
||||
echo "=== 文件上传测试 ==="
|
||||
assert_post_multipart "$BASE/upload" "multipart 文件上传"
|
||||
|
||||
echo ""
|
||||
echo "=== TLS/HTTPS 测试 ==="
|
||||
# 启动 HTTPS 服务器(使用测试证书)
|
||||
kill_server
|
||||
sleep 1
|
||||
$SERVER -r "$ROOT" -p 9999 --cert tests/server.crt --key tests/server.key > "$TMPDIR/server_tls.log" 2>&1 &
|
||||
pid_tls=$!
|
||||
|
||||
# 等待服务器就绪(curl -k 忽略证书验证)
|
||||
for i in {1..50}; do
|
||||
if curl -s -o /dev/null -k --max-time 2 "https://$HOST/" 2>/dev/null; then
|
||||
break
|
||||
fi
|
||||
sleep 0.2
|
||||
done
|
||||
|
||||
# 使用 openssl s_client 验证 TLS 握手
|
||||
if command -v openssl >/dev/null 2>&1; then
|
||||
tls_resp=$(echo -e "GET / HTTP/1.1\r\nHost: $HOST\r\nConnection: close\r\n\r\n" | \
|
||||
timeout 5 openssl s_client -connect "$HOST" -quiet 2>/dev/null | head -20)
|
||||
if echo "$tls_resp" | grep -q "HTTP/1.1"; then
|
||||
echo " ✓ TLS 握手 + HTTP GET — 通过 HTTPS 获取响应"
|
||||
pass
|
||||
else
|
||||
echo " ✗ TLS 握手 + HTTP GET — 未能通过 HTTPS 获取响应"
|
||||
fail
|
||||
fi
|
||||
else
|
||||
echo " ⚠ openssl 未安装,跳过 TLS 握手验证"
|
||||
fi
|
||||
|
||||
# 使用 curl -k 验证 HTTPS 响应体
|
||||
assert_body_contains "https://$HOST/" "Cocoon" "HTTPS 首页响应"
|
||||
|
||||
# 停止 HTTPS 服务器,恢复 HTTP 服务器
|
||||
kill_server
|
||||
sleep 1
|
||||
start_server
|
||||
|
||||
echo ""
|
||||
echo "=== 结果汇总 ==="
|
||||
echo "通过: $PASS"
|
||||
|
||||
19
tests/server.crt
Normal file
19
tests/server.crt
Normal file
@ -0,0 +1,19 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDCTCCAfGgAwIBAgIUPVEDY05Y47B5Xs305idz++X3wrgwDQYJKoZIhvcNAQEL
|
||||
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDYwNDA0MTkzM1oXDTI2MDYw
|
||||
NTA0MTkzM1owFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
|
||||
AAOCAQ8AMIIBCgKCAQEArRpnw9qH9mw3Px8FvRhgyVqMA1B0sJTlareiHNnIh9kP
|
||||
W2q2M0alzPiOllysEcEQn//FPPh/cH6gXhsQziNTRK7VPrkCV8+4jV/FbdG74WJx
|
||||
GMliemPwUvOKf+oLU/OFuyLvbze3VG+Wcwg/tdUxRUwHwu9nDj1F0nCbtGQBV+m3
|
||||
GS0diluKyYWE2rn54H6F1tnJcNNnfPGXptvFGKgI/6HVret28VYX9iMILy4UnA4H
|
||||
j7p8+MMyhDXqOi9y+nSPTXM2HaRUSgA0tWxlHsR9gE75+3bNRVU+3GzJaM6lDxzf
|
||||
jAHQgGWHwibCPJFX4eAgJseromlQKKAtLdToa4ngjwIDAQABo1MwUTAdBgNVHQ4E
|
||||
FgQUTPTOzkZPBbV1xHfZmPCxv4CQ7LUwHwYDVR0jBBgwFoAUTPTOzkZPBbV1xHfZ
|
||||
mPCxv4CQ7LUwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEADBVC
|
||||
KECbcbb8rBPmZkh2sJDtuXnW403+1Tg/YsUoSpLxa8Jq35JWfMR5fnmCAsaOxuNQ
|
||||
rFXNotVxLhaMK7IwIsyazdRkRAT4SLLLx5bt3p6BY4L9C/SUVlSK0SC1E39AGPB/
|
||||
vwce9xB4wmUj3o+zcn09r9ARhnr4ccMaOaHiX/opd6T+62XKoer/tSTvx31t44gW
|
||||
o5IPz7dk20g379jb1PTnq5lJWD1E1ttlRbETlivuMOwg3GjVWRc7smk7AlBnsems
|
||||
LzyrpySsvAsY/mLbwSJfNlSd00T/VV8WFUCkr/pm0l7WWPx2nHrUu9t4YjJ0UHY/
|
||||
UQNisxe3vGPxoTpD+A==
|
||||
-----END CERTIFICATE-----
|
||||
28
tests/server.key
Normal file
28
tests/server.key
Normal file
@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCtGmfD2of2bDc/
|
||||
HwW9GGDJWowDUHSwlOVqt6Ic2ciH2Q9barYzRqXM+I6WXKwRwRCf/8U8+H9wfqBe
|
||||
GxDOI1NErtU+uQJXz7iNX8Vt0bvhYnEYyWJ6Y/BS84p/6gtT84W7Iu9vN7dUb5Zz
|
||||
CD+11TFFTAfC72cOPUXScJu0ZAFX6bcZLR2KW4rJhYTaufngfoXW2clw02d88Zem
|
||||
28UYqAj/odWt63bxVhf2IwgvLhScDgePunz4wzKENeo6L3L6dI9NczYdpFRKADS1
|
||||
bGUexH2ATvn7ds1FVT7cbMlozqUPHN+MAdCAZYfCJsI8kVfh4CAmx6uiaVAooC0t
|
||||
1OhrieCPAgMBAAECggEAAOeW7DybokuNfFx2pLbYZqT8/1Vvzq5whn7AL6Npu2AE
|
||||
hwdHPXxciHYyFJIWah9WrWdiSf/IdreKsgM1MsaXfE/nmGTSINBamA6MkxbaAwMH
|
||||
MUh4JciY7G4OZr274mvu4nv7wVqKsDvHDwHXh2U+VSzhC4FByh7C2ycwgSBWq3Vw
|
||||
hm6D/8QOU7ZkIkaMWGc82KgjAXm8jrG2K5k0WWw5IDItQmX7M/ZYN5FS7ArXtbqw
|
||||
prlr1QdIWQvi/UtG0MQ2ys5ZRqbDTTfwAw06oIhq7e6zqMEjCD9G/WLGwqkRhe86
|
||||
hVcZ2DM01ge7Pva9XKNxIuh7DMrFnacAE3nnb1w0iQKBgQDZrfpllwKDIGNSauVD
|
||||
rejbstJDocMlWhuQsj2r+29nnFCqS8OEmG8E9r0uDZpCTUWzY2EgYb8cvTaFI7vs
|
||||
3owH1pF9nZRr1NZFeli4EuNEJFF10Qf8wp3mwTXweBckDW6ZwWfkgOKWJhAB9Eg7
|
||||
7GFlpAzgiJmeTa5ztCQpLqwcBwKBgQDLk4ea9quNoF/54U50dWs/Vb9F0X+aYupP
|
||||
OdmPHYpwfaAO7KCVVSE9qufqTRrrqssIvstODWZ3clxszQC1iiJpjTMhhCTfIOW+
|
||||
p6ZqU/mLUQ3kPsQuxoCagcn8HhCvuD4wcBG/rIHLr9iBuYjFuFnl+XxpuqUowXG9
|
||||
mpfGJBiFOQKBgCzFKpXQXDTp6WOfFq43y4e8HKDKWV+KS9cTwbloij4uGS0dTgYs
|
||||
b9D+imQ5afuu9uHxU52cXPklNVxmwBT0pKpMCDpkuY0ABl3dzvd08wclzRdFObZT
|
||||
tLQsEtL1cBT7Kj83Vw3ZMEEfbPVp751bfaCw//ECR04WvgWQDEvoDH/rAoGBAJEe
|
||||
g+ZlvPliv8gjYogGRAOMHqOoqivmI12CwaIRzuRNyVGaQQby/pU1RclyWaRxxfZf
|
||||
/BGR3996OISexsiixdE0hKR5zMb6bowweqMFZbh4eUyhqbwmaPq7Vr9Qt1hsIHjr
|
||||
Ctv2HziSC+a92dAIkJ0t8hB0qtOYwnoEv/jRNmGRAoGBAMUo7zXuvPi11es6L/1S
|
||||
kzh00RV7JdSOZ7cyEdzaGBMeMRpAFrjoy5kiXUj2mPuESqd3FiTR9dgYHrZBirfI
|
||||
mmCQxp8PZ5f+G2M5JbRslZfinPL7ePX/fijJ1kWMUGVuk3502tf5HjgE9k1/i0He
|
||||
TIi3EoBZvP9v9Y1ZUom5IFct
|
||||
-----END PRIVATE KEY-----
|
||||
@ -126,7 +126,7 @@ void test_merge_override_all(void) {
|
||||
.brotli_enabled = false
|
||||
};
|
||||
config_merge(&base, &cmdline,
|
||||
true, true, true, true, true, true, true, true);
|
||||
true, true, true, true, true, true, true, true, true, true, true);
|
||||
TEST_ASSERT_EQUAL_STRING("/new", base.root_dir);
|
||||
TEST_ASSERT_EQUAL(9090, base.port);
|
||||
TEST_ASSERT_TRUE(base.threaded);
|
||||
@ -155,7 +155,7 @@ void test_merge_no_override(void) {
|
||||
.log_level = LOG_LEVEL_DEBUG
|
||||
};
|
||||
config_merge(&base, &cmdline,
|
||||
false, false, false, false, false, false, false, false);
|
||||
false, false, false, false, false, false, false, false, false, false, false);
|
||||
TEST_ASSERT_EQUAL_STRING("/old", base.root_dir);
|
||||
TEST_ASSERT_EQUAL(8080, base.port);
|
||||
TEST_ASSERT_FALSE(base.threaded);
|
||||
@ -178,7 +178,7 @@ void test_merge_partial_override(void) {
|
||||
.log_level = LOG_LEVEL_DEBUG
|
||||
};
|
||||
config_merge(&base, &cmdline,
|
||||
true, false, false, true, false, false, false, false);
|
||||
true, false, false, true, false, false, false, false, false, false, false);
|
||||
TEST_ASSERT_EQUAL_STRING("/new", base.root_dir); /* overridden */
|
||||
TEST_ASSERT_EQUAL(8080, base.port); /* not overridden */
|
||||
TEST_ASSERT_EQUAL(2, base.num_workers); /* not overridden */
|
||||
@ -243,7 +243,7 @@ void test_merge_cmdline_null_root_dir(void) {
|
||||
/* cmdline root_dir 为 NULL,不应覆盖 base */
|
||||
cocoon_config_t base = {.root_dir = strdup("/old"), .port = 8080};
|
||||
cocoon_config_t cmdline = {.root_dir = NULL, .port = 9090};
|
||||
config_merge(&base, &cmdline, true, true, false, false, false, false, false, false);
|
||||
config_merge(&base, &cmdline, true, true, false, false, false, false, false, false, false, false, false);
|
||||
TEST_ASSERT_EQUAL_STRING("/old", base.root_dir); /* NULL 不覆盖 */
|
||||
TEST_ASSERT_EQUAL(9090, base.port); /* port 覆盖 */
|
||||
free((void *)base.root_dir);
|
||||
@ -251,7 +251,7 @@ void test_merge_cmdline_null_root_dir(void) {
|
||||
|
||||
void test_merge_null_safety(void) {
|
||||
/* 不应 crash */
|
||||
config_merge(NULL, NULL, true, true, true, true, true, true, true, true);
|
||||
config_merge(NULL, NULL, true, true, true, true, true, true, true, true, true, true, true);
|
||||
TEST_ASSERT_TRUE(1);
|
||||
}
|
||||
|
||||
|
||||
325
tls.c
Normal file
325
tls.c
Normal file
@ -0,0 +1,325 @@
|
||||
/**
|
||||
* tls.c - TLS/SSL 模块实现
|
||||
*
|
||||
* 使用 OpenSSL Memory BIO 与 coco 协程 I/O 集成:
|
||||
* - SSL_read 从 read BIO 消费解密数据
|
||||
* - SSL_write 向 write BIO 产出加密数据
|
||||
* - 网络 I/O 由 coco_read/coco_write 或 read/write 完成
|
||||
*
|
||||
* @author xfy
|
||||
*/
|
||||
|
||||
#include "tls.h"
|
||||
#include "log.h"
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../coco/include/coco.h"
|
||||
|
||||
/* 内部:TLS 连接条目 */
|
||||
typedef struct {
|
||||
SSL *ssl;
|
||||
BIO *rbio; /* 从 socket 读取的加密数据写入此处 */
|
||||
BIO *wbio; /* SSL_write 产出的加密数据从此读取 */
|
||||
} tls_conn_t;
|
||||
|
||||
/* fd → TLS 连接映射(动态数组,O(1) 索引) */
|
||||
static tls_conn_t **g_map = NULL;
|
||||
static int g_map_cap = 0;
|
||||
static SSL_CTX *g_ctx = NULL;
|
||||
|
||||
/* 内部:O(1) fd 查表 */
|
||||
static tls_conn_t* tls_lookup(int fd) {
|
||||
if (fd >= 0 && fd < g_map_cap) return g_map[fd];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void tls_map_set(int fd, tls_conn_t *t) {
|
||||
if (fd >= g_map_cap) {
|
||||
int old_cap = g_map_cap;
|
||||
g_map_cap = fd + 1;
|
||||
g_map = (tls_conn_t **)realloc(g_map, g_map_cap * sizeof(tls_conn_t *));
|
||||
for (int i = old_cap; i < g_map_cap; i++) g_map[i] = NULL;
|
||||
}
|
||||
g_map[fd] = t;
|
||||
}
|
||||
|
||||
static void tls_map_clear(int fd) {
|
||||
if (fd >= 0 && fd < g_map_cap) g_map[fd] = NULL;
|
||||
}
|
||||
|
||||
/* 内部:从 socket 读取原始数据(协程感知) */
|
||||
static ssize_t socket_read(int fd, void *buf, size_t len) {
|
||||
if (coco_sched_get_current() != NULL) {
|
||||
return coco_read(fd, buf, len);
|
||||
}
|
||||
return read(fd, buf, len);
|
||||
}
|
||||
|
||||
/* 内部:向 socket 写入原始数据 */
|
||||
static ssize_t socket_write_all(int fd, const void *buf, size_t len) {
|
||||
if (coco_sched_get_current() != NULL) {
|
||||
size_t sent = 0;
|
||||
while (sent < len) {
|
||||
ssize_t n = coco_write(fd, (const char *)buf + sent, len - sent);
|
||||
if (n > 0) {
|
||||
sent += (size_t)n;
|
||||
} else if (n < 0) {
|
||||
if (n == -EAGAIN || n == -EINTR || n == COCO_ERROR_WOULD_BLOCK) continue;
|
||||
return -1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return (ssize_t)sent;
|
||||
} else {
|
||||
size_t sent = 0;
|
||||
while (sent < len) {
|
||||
ssize_t n = write(fd, (const char *)buf + sent, len - sent);
|
||||
if (n > 0) {
|
||||
sent += (size_t)n;
|
||||
} else if (n < 0) {
|
||||
if (errno == EAGAIN || errno == EINTR) continue;
|
||||
return -1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return (ssize_t)sent;
|
||||
}
|
||||
}
|
||||
|
||||
/* 内部:将 write BIO 中的加密数据刷到 socket */
|
||||
static int flush_wbio(int fd, BIO *wbio) {
|
||||
char buf[16384];
|
||||
int pending = BIO_read(wbio, buf, sizeof(buf));
|
||||
while (pending > 0) {
|
||||
if (socket_write_all(fd, buf, (size_t)pending) < 0) return -1;
|
||||
pending = BIO_read(wbio, buf, sizeof(buf));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== 公共 API ===== */
|
||||
|
||||
int tls_create_context(const char *cert_path, const char *key_path) {
|
||||
if (!cert_path || !key_path) return -1;
|
||||
|
||||
SSL_library_init();
|
||||
OpenSSL_add_all_algorithms();
|
||||
SSL_load_error_strings();
|
||||
|
||||
const SSL_METHOD *method = TLS_server_method();
|
||||
if (!method) return -1;
|
||||
|
||||
g_ctx = SSL_CTX_new(method);
|
||||
if (!g_ctx) return -1;
|
||||
|
||||
/* 最低 TLS 1.2 */
|
||||
SSL_CTX_set_min_proto_version(g_ctx, TLS1_2_VERSION);
|
||||
|
||||
/* 加载证书 */
|
||||
if (SSL_CTX_use_certificate_file(g_ctx, cert_path, SSL_FILETYPE_PEM) <= 0) {
|
||||
log_error("加载 TLS 证书失败: %s", cert_path);
|
||||
SSL_CTX_free(g_ctx);
|
||||
g_ctx = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 加载私钥 */
|
||||
if (SSL_CTX_use_PrivateKey_file(g_ctx, key_path, SSL_FILETYPE_PEM) <= 0) {
|
||||
log_error("加载 TLS 私钥失败: %s", key_path);
|
||||
SSL_CTX_free(g_ctx);
|
||||
g_ctx = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 验证私钥与证书匹配 */
|
||||
if (!SSL_CTX_check_private_key(g_ctx)) {
|
||||
log_error("TLS 私钥与证书不匹配");
|
||||
SSL_CTX_free(g_ctx);
|
||||
g_ctx = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("TLS 上下文已初始化: cert=%s", cert_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tls_destroy_context(void) {
|
||||
if (g_ctx) {
|
||||
SSL_CTX_free(g_ctx);
|
||||
g_ctx = NULL;
|
||||
}
|
||||
if (g_map) {
|
||||
for (int i = 0; i < g_map_cap; i++) {
|
||||
if (g_map[i]) {
|
||||
if (g_map[i]->ssl) SSL_free(g_map[i]->ssl);
|
||||
if (g_map[i]->rbio) BIO_free(g_map[i]->rbio);
|
||||
if (g_map[i]->wbio) BIO_free(g_map[i]->wbio);
|
||||
free(g_map[i]);
|
||||
}
|
||||
}
|
||||
free(g_map);
|
||||
g_map = NULL;
|
||||
g_map_cap = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool tls_has_context(void) {
|
||||
return g_ctx != NULL;
|
||||
}
|
||||
|
||||
int tls_accept(int fd) {
|
||||
if (!g_ctx) return -1;
|
||||
|
||||
tls_conn_t *t = (tls_conn_t *)calloc(1, sizeof(tls_conn_t));
|
||||
if (!t) return -1;
|
||||
|
||||
t->ssl = SSL_new(g_ctx);
|
||||
if (!t->ssl) {
|
||||
free(t);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 创建 Memory BIO 对 */
|
||||
t->rbio = BIO_new(BIO_s_mem());
|
||||
t->wbio = BIO_new(BIO_s_mem());
|
||||
if (!t->rbio || !t->wbio) {
|
||||
SSL_free(t->ssl);
|
||||
free(t);
|
||||
return -1;
|
||||
}
|
||||
|
||||
BIO_set_mem_eof_return(t->rbio, -1);
|
||||
BIO_set_mem_eof_return(t->wbio, -1);
|
||||
SSL_set_bio(t->ssl, t->rbio, t->wbio);
|
||||
SSL_set_accept_state(t->ssl);
|
||||
|
||||
tls_map_set(fd, t);
|
||||
|
||||
/* 执行握手循环 */
|
||||
while (1) {
|
||||
int ret = SSL_do_handshake(t->ssl);
|
||||
if (ret == 1) {
|
||||
log_debug("TLS 握手完成 fd=%d", fd);
|
||||
return 0; /* 成功 */
|
||||
}
|
||||
|
||||
int err = SSL_get_error(t->ssl, ret);
|
||||
if (err == SSL_ERROR_WANT_READ) {
|
||||
/* 尝试先刷出 write BIO 中的数据 */
|
||||
if (flush_wbio(fd, t->wbio) < 0) goto fail;
|
||||
|
||||
/* 从 socket 读取加密数据 */
|
||||
char buf[8192];
|
||||
ssize_t n = socket_read(fd, buf, sizeof(buf));
|
||||
if (n > 0) {
|
||||
BIO_write(t->rbio, buf, (int)n);
|
||||
} else if (n < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
|
||||
/* 非阻塞模式下暂时无数据,继续尝试 */
|
||||
continue;
|
||||
}
|
||||
goto fail;
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
} else if (err == SSL_ERROR_WANT_WRITE) {
|
||||
if (flush_wbio(fd, t->wbio) < 0) goto fail;
|
||||
} else {
|
||||
log_error("TLS 握手失败 fd=%d, err=%d", fd, err);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
tls_map_clear(fd);
|
||||
SSL_free(t->ssl);
|
||||
free(t);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t tls_read(int fd, void *buf, size_t len) {
|
||||
tls_conn_t *t = tls_lookup(fd);
|
||||
if (!t || !t->ssl) return -1;
|
||||
|
||||
while (1) {
|
||||
int ret = SSL_read(t->ssl, buf, (int)len);
|
||||
if (ret > 0) return (ssize_t)ret;
|
||||
|
||||
int err = SSL_get_error(t->ssl, ret);
|
||||
if (err == SSL_ERROR_WANT_READ) {
|
||||
/* 从 socket 读取加密数据 */
|
||||
char tmp[8192];
|
||||
ssize_t n = socket_read(fd, tmp, sizeof(tmp));
|
||||
if (n > 0) {
|
||||
BIO_write(t->rbio, tmp, (int)n);
|
||||
} else {
|
||||
return n; /* 0 或 -1 */
|
||||
}
|
||||
} else if (err == SSL_ERROR_WANT_WRITE) {
|
||||
if (flush_wbio(fd, t->wbio) < 0) return -1;
|
||||
} else if (err == SSL_ERROR_ZERO_RETURN) {
|
||||
return 0; /* 对端关闭 */
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t tls_write(int fd, const void *buf, size_t len) {
|
||||
tls_conn_t *t = tls_lookup(fd);
|
||||
if (!t || !t->ssl) return -1;
|
||||
|
||||
size_t total = 0;
|
||||
while (total < len) {
|
||||
int ret = SSL_write(t->ssl, (const char *)buf + total, (int)(len - total));
|
||||
if (ret > 0) {
|
||||
total += (size_t)ret;
|
||||
/* 刷出 write BIO */
|
||||
if (flush_wbio(fd, t->wbio) < 0) return -1;
|
||||
} else {
|
||||
int err = SSL_get_error(t->ssl, ret);
|
||||
if (err == SSL_ERROR_WANT_READ) {
|
||||
char tmp[8192];
|
||||
ssize_t n = socket_read(fd, tmp, sizeof(tmp));
|
||||
if (n > 0) {
|
||||
BIO_write(t->rbio, tmp, (int)n);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else if (err == SSL_ERROR_WANT_WRITE) {
|
||||
if (flush_wbio(fd, t->wbio) < 0) return -1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (ssize_t)total;
|
||||
}
|
||||
|
||||
void tls_close(int fd) {
|
||||
tls_conn_t *t = tls_lookup(fd);
|
||||
if (!t) return;
|
||||
|
||||
if (t->ssl) {
|
||||
SSL_shutdown(t->ssl);
|
||||
/* 刷出最后的 close_notify */
|
||||
flush_wbio(fd, t->wbio);
|
||||
SSL_free(t->ssl);
|
||||
}
|
||||
/* rbio/wbio 已被 SSL_free 释放(SSL_set_bio 转移所有权) */
|
||||
|
||||
tls_map_clear(fd);
|
||||
free(t);
|
||||
}
|
||||
|
||||
bool tls_has_connection(int fd) {
|
||||
return tls_lookup(fd) != NULL;
|
||||
}
|
||||
92
tls.h
Normal file
92
tls.h
Normal file
@ -0,0 +1,92 @@
|
||||
/**
|
||||
* tls.h - TLS/SSL 模块接口
|
||||
*
|
||||
* 基于 OpenSSL 实现 HTTPS 支持,使用 Memory BIO 与 coco 协程 I/O 集成。
|
||||
*
|
||||
* 设计:
|
||||
* - 全局 SSL_CTX 管理证书和私钥
|
||||
* - 每个连接独立的 SSL* + Memory BIO 对
|
||||
* - fd→SSL 映射表,使 send_all 等现有代码透明支持 TLS
|
||||
* - SSL_read/write 的 WANT_READ/WANT_WRITE 通过 coco_read/write 协程化
|
||||
*
|
||||
* @author xfy
|
||||
*/
|
||||
|
||||
#ifndef COCOON_TLS_H
|
||||
#define COCOON_TLS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
/**
|
||||
* tls_create_context - 创建全局 TLS 上下文
|
||||
*
|
||||
* 加载证书和私钥,初始化 OpenSSL。
|
||||
*
|
||||
* @param cert_path PEM 格式证书路径
|
||||
* @param key_path PEM 格式私钥路径
|
||||
* @return 0 成功,-1 失败
|
||||
*/
|
||||
int tls_create_context(const char *cert_path, const char *key_path);
|
||||
|
||||
/**
|
||||
* tls_destroy_context - 销毁全局 TLS 上下文
|
||||
*/
|
||||
void tls_destroy_context(void);
|
||||
|
||||
/**
|
||||
* tls_has_context - 检查 TLS 上下文是否已创建
|
||||
*/
|
||||
bool tls_has_context(void);
|
||||
|
||||
/**
|
||||
* tls_accept - 为指定 fd 创建 SSL 连接并完成握手
|
||||
*
|
||||
* 在协程上下文中调用,WANT_READ/WANT_WRITE 时自动 yield。
|
||||
*
|
||||
* @param fd 已连接的客户端 socket
|
||||
* @return 0 成功,-1 失败
|
||||
*/
|
||||
int tls_accept(int fd);
|
||||
|
||||
/**
|
||||
* tls_read - 从 TLS 连接读取解密后的数据
|
||||
*
|
||||
* 在协程上下文中调用,数据不足时自动 yield。
|
||||
*
|
||||
* @param fd 客户端 socket
|
||||
* @param buf 输出缓冲区
|
||||
* @param len 最大读取字节数
|
||||
* @return 读取字节数,0 对端关闭,-1 错误
|
||||
*/
|
||||
ssize_t tls_read(int fd, void *buf, size_t len);
|
||||
|
||||
/**
|
||||
* tls_write - 向 TLS 连接写入明文数据(自动加密后发送)
|
||||
*
|
||||
* @param fd 客户端 socket
|
||||
* @param buf 待发送数据
|
||||
* @param len 数据长度
|
||||
* @return 写入字节数(明文),-1 错误
|
||||
*/
|
||||
ssize_t tls_write(int fd, const void *buf, size_t len);
|
||||
|
||||
/**
|
||||
* tls_close - 关闭 TLS 连接并释放资源
|
||||
*
|
||||
* 执行 SSL_shutdown,清理 fd 映射。
|
||||
*
|
||||
* @param fd 客户端 socket
|
||||
*/
|
||||
void tls_close(int fd);
|
||||
|
||||
/**
|
||||
* tls_has_connection - 检查 fd 是否关联了 TLS 连接
|
||||
*
|
||||
* @param fd 客户端 socket
|
||||
* @return true 是 TLS 连接
|
||||
*/
|
||||
bool tls_has_connection(int fd);
|
||||
|
||||
#endif /* COCOON_TLS_H */
|
||||
Loading…
x
Reference in New Issue
Block a user