diff --git a/docs/cn/hloop.md b/docs/cn/hloop.md index 065be1f38..8b6be87bc 100644 --- a/docs/cn/hloop.md +++ b/docs/cn/hloop.md @@ -271,6 +271,8 @@ hwrite_cb hio_getcb_write(hio_t* io); // 获取关闭回调 hclose_cb hio_getcb_close(hio_t* io); +// 回调语义见下文“IO回调语义与缓冲区生命周期”。 + // 开启SSL/TLS加密通信 int hio_enable_ssl(hio_t* io); // 是否SSL/TLS加密通信 @@ -311,6 +313,7 @@ int hio_accept (hio_t* io); // 连接 // connect => hio_add(io, HV_WRITE) => hconnect_cb +// connect_cb表示应用层连接可用;配置代理或TLS时会在相应握手全部成功后才调用。 int hio_connect(hio_t* io); // 读 @@ -347,10 +350,12 @@ int hio_read_until_delim (hio_t* io, unsigned char delim); // 写 // hio_try_write => hio_add(io, HV_WRITE) => write => hwrite_cb +// write_cb报告实际写出的字节数;使用hio_write_is_complete判断写队列是否排空。 int hio_write (hio_t* io, const void* buf, size_t len); // 关闭 // hio_del(io, HV_RDWR) => close => hclose_cb +// close_cb在一次hio生命周期中最多调用一次,适合释放连接关联的上下文。 int hio_close (hio_t* io); // 异步关闭 (投递一个close事件) @@ -644,6 +649,14 @@ int hio_set_kcp(hio_t* io, kcp_setting_t* setting DEFAULT(NULL)); ``` +### IO回调语义与缓冲区生命周期 + +- `connect_cb` 表示应用层连接已可用:若设置了代理或TLS,代理握手和TLS握手均已成功;它并不只是TCP三次握手完成。 +- `write_cb` 的 `writebytes` 表示本次实际写出的字节数,不表示一条业务消息已经全部发送;需要通过 `hio_write_is_complete(io)` 判断写队列是否排空。 +- `read_cb` 和 `write_cb` 都可能同步触发:例如 `hio_read` 处理已有读缓存,或 `hio_write` 立即写入成功时。因此回调代码必须能处理可重入调用。 +- `close_cb` 在一次 `hio_t` 生命周期内最多触发一次,是释放连接关联上下文的合适位置。 +- `read_cb` / `write_cb` 收到的 `buf` 由libhv借用提供,只保证在本次回调返回前有效;需要保存或跨异步边界使用时必须自行复制。 + 示例代码: - 事件循环: [examples/hloop_test.c](../../examples/hloop_test.c) diff --git a/event/README.md b/event/README.md index 87782e897..2bf978048 100644 --- a/event/README.md +++ b/event/README.md @@ -9,6 +9,7 @@ ├── unpack.h 拆包 ├── rudp.h 可靠UDP ├── proxy.c 代理 +├── tls.c TLS握手 ├── socks5.c SOCKS5代理 ├── iowatcher.h IO多路复用统一抽象接口 ├── select.c EVENT_SELECT实现 diff --git a/event/hevent.c b/event/hevent.c index 44f12d485..fdc8c5fee 100644 --- a/event/hevent.c +++ b/event/hevent.c @@ -65,6 +65,7 @@ static void hio_socket_init(hio_t* io) { } void hio_init(hio_t* io) { + io->phase = HIO_PHASE_NONE; // alloc localaddr,peeraddr when hio_socket_init /* if (io->localaddr == NULL) { @@ -94,6 +95,7 @@ void hio_ready(hio_t* io) { // public: io->id = hio_next_id(); io->io_type = HIO_TYPE_UNKNOWN; + io->phase = HIO_PHASE_READY; io->error = 0; io->events = io->revents = 0; io->last_read_hrtime = io->last_write_hrtime = io->loop->cur_hrtime; @@ -426,6 +428,7 @@ void hio_write_cb(hio_t* io, const void* buf, int len) { void hio_close_cb(hio_t* io) { io->connected = 0; io->closed = 1; + io->phase = HIO_PHASE_CLOSED; hclose_cb close_cb = io->close_cb; if (close_cb) { // printd("close_cb------\n"); diff --git a/event/hevent.h b/event/hevent.h index a844879ae..2ef1ae715 100644 --- a/event/hevent.h +++ b/event/hevent.h @@ -24,6 +24,22 @@ #define HIO_READ_UNTIL_LENGTH 0x2 #define HIO_READ_UNTIL_DELIM 0x4 +// Internal NIO transport lifecycle. hio_handle_events is the single readiness +// dispatcher; phase selects its current transport or handshake action. +typedef enum { + HIO_PHASE_NONE = 0, + HIO_PHASE_READY, + HIO_PHASE_ACCEPTING, + HIO_PHASE_CONNECTING, + HIO_PHASE_PROXY_HANDSHAKING, + HIO_PHASE_PROXY_ESTABLISHED, + HIO_PHASE_TLS_SERVER_HANDSHAKING, + HIO_PHASE_TLS_CLIENT_HANDSHAKING, + HIO_PHASE_TLS_ESTABLISHED, + HIO_PHASE_ESTABLISHED, + HIO_PHASE_CLOSED, +} hio_phase_e; + ARRAY_DECL(hio_t*, io_array); ARRAY_DECL(hsignal_t*, signal_array); QUEUE_DECL(hevent_t, event_queue); @@ -115,7 +131,7 @@ struct hperiod_s { }; QUEUE_DECL(offset_buf_t, write_queue); -// sizeof(struct hio_s)=416 on linux-x64 +// sizeof(struct hio_s)=424 on linux-x64 struct hio_s { HEVENT_FIELDS // flags @@ -134,6 +150,7 @@ struct hio_s { unsigned alloced_ssl_ctx :1; // for hio_new_ssl_ctx // public: hio_type_e io_type; + hio_phase_e phase; uint32_t id; // fd cannot be used as unique identifier, so we provide an id int fd; int error; diff --git a/event/hloop.c b/event/hloop.c index 133df4fd2..4a0acd814 100644 --- a/event/hloop.c +++ b/event/hloop.c @@ -898,6 +898,11 @@ int hio_add(hio_t* io, hio_cb cb, int events) { io->events |= add_events; if (!io->active) { + // A NULL callback only changes the watched mask. Keep the transport + // dispatcher installed by the initial hio_add call. + if (cb == NULL) { + cb = (hio_cb)io->cb; + } EVENT_ADD(loop, io, cb); loop->nios++; } diff --git a/event/hloop.h b/event/hloop.h index 4716fee17..8855b93c1 100644 --- a/event/hloop.h +++ b/event/hloop.h @@ -30,6 +30,14 @@ typedef void (*hread_cb) (hio_t* io, void* buf, int readbytes); typedef void (*hwrite_cb) (hio_t* io, const void* buf, int writebytes); typedef void (*hclose_cb) (hio_t* io); +/* + * Callback buffer lifetime: buf passed to hread_cb/hwrite_cb is borrowed from + * libhv and is valid only until the callback returns. Copy it before retaining + * it or using it asynchronously. hio_read/hio_write can deliver a callback + * synchronously (for buffered data or an immediately writable socket), so + * callback code must tolerate re-entry. + */ + typedef enum { HLOOP_STATUS_STOP, HLOOP_STATUS_RUNNING, @@ -423,6 +431,8 @@ HV_EXPORT void hio_set_heartbeat(hio_t* io, int interval_ms, hio_send_heartbeat_ HV_EXPORT int hio_accept (hio_t* io); // connect => hio_add(io, HV_WRITE) => hconnect_cb +// hconnect_cb means the application connection is ready: any configured proxy +// and TLS handshakes have completed. It is not merely TCP connect completion. HV_EXPORT int hio_connect(hio_t* io); // hio_add(io, HV_READ) => read => hread_cb @@ -444,12 +454,15 @@ HV_EXPORT int hio_read_remain(hio_t* io); #define hio_read_until(io, len) hio_read_until_length(io, len) // NOTE: hio_write is thread-safe, locked by recursive_mutex, allow to be called by other threads. -// hio_try_write => hio_add(io, HV_WRITE) => write => hwrite_cb +// hio_try_write => hio_add(io, HV_WRITE) => write => hwrite_cb. hwrite_cb +// reports bytes written, not completion of a logical application message; use +// hio_write_is_complete(io) to determine whether the write queue is empty. HV_EXPORT int hio_write (hio_t* io, const void* buf, size_t len); HV_EXPORT int hio_sendto (hio_t* io, const void* buf, size_t len, struct sockaddr* addr); // NOTE: hio_close is thread-safe, hio_close_async will be called actually in other thread. -// hio_del(io, HV_RDWR) => close => hclose_cb +// hio_del(io, HV_RDWR) => close => hclose_cb. hclose_cb is invoked at most once +// per hio lifecycle and is the place to release connection-associated state. HV_EXPORT int hio_close (hio_t* io); // NOTE: hloop_post_event(hio_close_event) HV_EXPORT int hio_close_async(hio_t* io); diff --git a/event/nio.c b/event/nio.c index 7805cbac8..2438d520f 100644 --- a/event/nio.c +++ b/event/nio.c @@ -7,6 +7,7 @@ #include "herr.h" #include "hthread.h" #include "proxy.h" +#include "tls.h" static void __connect_timeout_cb(htimer_t* timer) { hio_t* io = (hio_t*)timer->privdata; @@ -66,68 +67,37 @@ static void __close_cb(hio_t* io) { hio_close_cb(io); } -static void ssl_server_handshake(hio_t* io) { - printd("ssl server handshake...\n"); - int ret = hssl_accept(io->ssl); - if (ret == 0) { - // handshake finish - hio_del(io, HV_RDWR); - printd("ssl handshake finished.\n"); - __accept_cb(io); - } - else if (ret == HSSL_WANT_READ) { - if (io->events & HV_WRITE) { - hio_del(io, HV_WRITE); - } - if ((io->events & HV_READ) == 0) { - hio_add(io, ssl_server_handshake, HV_READ); - } - } - else if (ret == HSSL_WANT_WRITE) { - if (io->events & HV_READ) { - hio_del(io, HV_READ); - } - if ((io->events & HV_WRITE) == 0) { - hio_add(io, ssl_server_handshake, HV_WRITE); - } - } - else { - hloge("ssl server handshake failed: %d", ret); - io->error = ERR_SSL_HANDSHAKE; - hio_close(io); - } +static void hio_handle_events(hio_t* io); + +static bool nio_is_establishing(hio_t* io) { + return io->phase >= HIO_PHASE_CONNECTING && + io->phase < HIO_PHASE_ESTABLISHED; } -static void ssl_client_handshake(hio_t* io) { - printd("ssl client handshake...\n"); - int ret = hssl_connect(io->ssl); - if (ret == 0) { - // handshake finish - hio_del(io, HV_RDWR); - printd("ssl handshake finished.\n"); - __connect_cb(io); - } - else if (ret == HSSL_WANT_READ) { - if (io->events & HV_WRITE) { - hio_del(io, HV_WRITE); - } - if ((io->events & HV_READ) == 0) { - hio_add(io, ssl_client_handshake, HV_READ); - } - } - else if (ret == HSSL_WANT_WRITE) { - if (io->events & HV_READ) { - hio_del(io, HV_READ); - } - if ((io->events & HV_WRITE) == 0) { - hio_add(io, ssl_client_handshake, HV_WRITE); - } +static void nio_flush_write_queue(hio_t* io) { + hrecursive_mutex_lock(&io->write_mutex); + bool pending = !write_queue_empty(&io->write_queue); + hrecursive_mutex_unlock(&io->write_mutex); + if (!io->closed && pending) { + hio_add(io, hio_handle_events, HV_WRITE); } - else { - hloge("ssl client handshake failed: %d", ret); - io->error = ERR_SSL_HANDSHAKE; - hio_close(io); +} + +static void nio_connect_ready(hio_t* io) { + io->phase = HIO_PHASE_ESTABLISHED; + __connect_cb(io); + // Keep proxy metadata available during connect_cb. The transport no longer + // needs it after the application has accepted the established connection. + if (io->proxy) { + proxy_ctx_free(io->proxy); + io->proxy = NULL; } + nio_flush_write_queue(io); +} + +static void nio_accept_ready(hio_t* io) { + io->phase = HIO_PHASE_ESTABLISHED; + __accept_cb(io); } static void nio_accept(hio_t* io) { @@ -160,34 +130,15 @@ static void nio_accept(hio_t* io) { } if (io->io_type == HIO_TYPE_SSL) { - if (connio->ssl == NULL) { - // io->ssl_ctx > g_ssl_ctx > hssl_ctx_new - hssl_ctx_t ssl_ctx = NULL; - if (io->ssl_ctx) { - ssl_ctx = io->ssl_ctx; - } else if (g_ssl_ctx) { - ssl_ctx = g_ssl_ctx; - } else { - io->ssl_ctx = ssl_ctx = hssl_ctx_new(NULL); - io->alloced_ssl_ctx = 1; - } - if (ssl_ctx == NULL) { - io->error = ERR_NEW_SSL_CTX; - goto accept_error; - } - hssl_t ssl = hssl_new(ssl_ctx, connfd); - if (ssl == NULL) { - io->error = ERR_NEW_SSL; - goto accept_error; - } - connio->ssl = ssl; + hio_add(connio, hio_handle_events, HV_READ); + if (tls_server_handshake_start(io, connio) == 0 && + connio->phase == HIO_PHASE_TLS_ESTABLISHED) { + nio_accept_ready(connio); } - hio_enable_ssl(connio); - ssl_server_handshake(connio); } else { // NOTE: SSL call accept_cb after handshake finished - __accept_cb(connio); + nio_accept_ready(connio); } } return; @@ -202,48 +153,14 @@ static void nio_accept(hio_t* io) { // handshake completed), start the SSL handshake or deliver connect_cb. static void nio_connect_established(hio_t* io) { if (io->io_type == HIO_TYPE_SSL) { - if (io->ssl == NULL) { - // io->ssl_ctx > g_ssl_ctx > hssl_ctx_new - hssl_ctx_t ssl_ctx = NULL; - if (io->ssl_ctx) { - ssl_ctx = io->ssl_ctx; - } else if (g_ssl_ctx) { - ssl_ctx = g_ssl_ctx; - } else { - io->ssl_ctx = ssl_ctx = hssl_ctx_new(NULL); - io->alloced_ssl_ctx = 1; - } - if (ssl_ctx == NULL) { - io->error = ERR_NEW_SSL_CTX; - hio_close(io); - return; - } - hssl_t ssl = hssl_new(ssl_ctx, io->fd); - if (ssl == NULL) { - io->error = ERR_NEW_SSL; - hio_close(io); - return; - } - io->ssl = ssl; + if (tls_client_handshake_start(io) == 0 && + io->phase == HIO_PHASE_TLS_ESTABLISHED) { + nio_connect_ready(io); } - // SNI: through a proxy the TLS peer is the target, so the proxy's - // target_host is authoritative; otherwise use the explicitly-set - // io->hostname. SNI must be a hostname, not an IP literal (RFC 6066), - // so a numeric candidate is skipped and the next one is considered. - const char* sni = NULL; - if (io->proxy && io->proxy->setting.target_host[0] && !is_ipaddr(io->proxy->setting.target_host)) { - sni = io->proxy->setting.target_host; - } else if (io->hostname && !is_ipaddr(io->hostname)) { - sni = io->hostname; - } - if (sni) { - hssl_set_sni_hostname(io->ssl, sni); - } - ssl_client_handshake(io); } else { // NOTE: SSL call connect_cb after handshake finished - __connect_cb(io); + nio_connect_ready(io); } } @@ -262,7 +179,7 @@ static void nio_connect(hio_t* io) { // Proxy: the TCP connection is to the proxy; run its protocol handshake // before TLS or the user connect callback. if (io->proxy) { - proxy_handshake_start(io, nio_connect_established); + proxy_handshake_start(io); return; } @@ -464,6 +381,32 @@ static void nio_write(hio_t* io) { } static void hio_handle_events(hio_t* io) { + hio_phase_e phase = io->phase; + if (phase == HIO_PHASE_PROXY_HANDSHAKING) { + int revents = io->revents; + io->revents = 0; + if (revents & HV_READ) { + proxy_handshake_read(io); + } + if (io->phase == HIO_PHASE_PROXY_ESTABLISHED) { + nio_connect_established(io); + } + return; + } + if (phase == HIO_PHASE_TLS_SERVER_HANDSHAKING || + phase == HIO_PHASE_TLS_CLIENT_HANDSHAKING) { + io->revents = 0; + tls_handshake_step(io); + if (io->phase == HIO_PHASE_TLS_ESTABLISHED) { + if (phase == HIO_PHASE_TLS_SERVER_HANDSHAKING) { + nio_accept_ready(io); + } else { + nio_connect_ready(io); + } + } + return; + } + if ((io->events & HV_READ) && (io->revents & HV_READ)) { if (io->accept) { nio_accept(io); @@ -497,10 +440,13 @@ static void hio_handle_events(hio_t* io) { int hio_accept(hio_t* io) { io->accept = 1; + io->phase = HIO_PHASE_ACCEPTING; return hio_add(io, hio_handle_events, HV_READ); } int hio_connect(hio_t* io) { + io->phase = HIO_PHASE_CONNECTING; + io->cb = (hevent_cb)hio_handle_events; int ret = connect(io->fd, io->peeraddr, SOCKADDR_LEN(io->peeraddr)); #ifdef OS_WIN if (ret < 0 && socket_errno() != WSAEWOULDBLOCK) { @@ -553,6 +499,10 @@ static int hio_write4 (hio_t* io, const void* buf, size_t len, struct sockaddr* } #endif if (write_queue_empty(&io->write_queue)) { + if (nio_is_establishing(io)) { + nwrite = 0; + goto enqueue; + } try_write: nwrite = __nio_write(io, buf, len, addr); // printd("write retval=%d\n", nwrite); @@ -575,7 +525,9 @@ static int hio_write4 (hio_t* io, const void* buf, size_t len, struct sockaddr* goto disconnect; } enqueue: - hio_add(io, hio_handle_events, HV_WRITE); + if (!nio_is_establishing(io)) { + hio_add(io, hio_handle_events, HV_WRITE); + } } if (nwrite < len) { size_t unwritten_len = len - nwrite; @@ -672,8 +624,10 @@ int hio_close (hio_t* io) { io->ssl_ctx = NULL; } SAFE_FREE(io->hostname); - proxy_ctx_free(io->proxy); - io->proxy = NULL; + if (io->proxy) { + proxy_ctx_free(io->proxy); + io->proxy = NULL; + } if (io->io_type & HIO_TYPE_SOCKET) { closesocket(io->fd); } else if (io->io_type == HIO_TYPE_PIPE) { diff --git a/event/proxy.c b/event/proxy.c index 410db6c92..98aa2f391 100644 --- a/event/proxy.c +++ b/event/proxy.c @@ -115,7 +115,7 @@ void proxy_handshake_fail(hio_t* io) { hio_close(io); } -int proxy_handshake_send(hio_t* io, const void* buf, int len) { +int proxy_handshake_write(hio_t* io, const void* buf, int len) { int flag = 0; #ifdef MSG_NOSIGNAL flag |= MSG_NOSIGNAL; @@ -126,11 +126,11 @@ int proxy_handshake_send(hio_t* io, const void* buf, int len) { void proxy_handshake_established(hio_t* io) { proxy_ctx_t* proxy = io->proxy; hio_del(io, HV_READ); - if (proxy == NULL || proxy->on_established == NULL) { + if (proxy == NULL) { proxy_handshake_fail(io); return; } - proxy->on_established(io); + io->phase = HIO_PHASE_PROXY_ESTABLISHED; } static void http_connect_client_handshake(hio_t* io) { @@ -184,21 +184,42 @@ static void http_connect_client_handshake(hio_t* io) { static void http_connect_client_start(hio_t* io) { char buf[2048]; int n = http_connect_build_request(io->proxy, buf, (int)sizeof(buf)); - if (n < 0 || proxy_handshake_send(io, buf, n) != 0) { + if (n < 0 || proxy_handshake_write(io, buf, n) != 0) { proxy_handshake_fail(io); return; } io->proxy->rlen = 0; - hio_add(io, http_connect_client_handshake, HV_READ); + hio_add(io, NULL, HV_READ); } -void proxy_handshake_start(hio_t* io, proxy_established_cb on_established) { +void proxy_handshake_read(hio_t* io) { + if (io->proxy == NULL) { + proxy_handshake_fail(io); + return; + } + switch (io->proxy->setting.protocol) { + case PROXY_PROTOCOL_SOCKS5: + socks5_client_handshake_read(io); + return; + case PROXY_PROTOCOL_HTTP_CONNECT: + http_connect_client_handshake(io); + return; + default: + proxy_handshake_fail(io); + return; + } +} + +void proxy_handshake_start(hio_t* io) { proxy_ctx_t* proxy = io->proxy; - if (proxy == NULL || on_established == NULL) { + if (proxy == NULL) { proxy_handshake_fail(io); return; } - proxy->on_established = on_established; + io->phase = HIO_PHASE_PROXY_HANDSHAKING; + if (io->events & HV_WRITE) { + hio_del(io, HV_WRITE); + } switch (proxy->setting.protocol) { case PROXY_PROTOCOL_SOCKS5: socks5_client_handshake_start(io); @@ -218,6 +239,11 @@ static void on_tcp_proxy_accept(hio_t* io) { if (proxy == NULL || hio_setup_tcp_upstream(io, proxy->setting.target_host, proxy->setting.target_port, 0) == NULL) { hio_close(io); + return; + } + if (io->proxy) { + proxy_ctx_free(io->proxy); + io->proxy = NULL; } } @@ -244,5 +270,9 @@ hio_t* hloop_create_udp_proxy_server(hloop_t* loop, const proxy_setting_t* setti hio_close(listener); return NULL; } + if (listener->proxy) { + proxy_ctx_free(listener->proxy); + listener->proxy = NULL; + } return listener; } diff --git a/event/proxy.h b/event/proxy.h index c5f17166e..ce4e3b74f 100644 --- a/event/proxy.h +++ b/event/proxy.h @@ -3,8 +3,6 @@ #include "hloop.h" -typedef void (*proxy_established_cb)(hio_t* io); - // Internal context held by hio_t::proxy. ctx is available to either client or // server proxy implementations; ctx_free, when set, owns its cleanup. typedef struct proxy_ctx_s { @@ -15,7 +13,6 @@ typedef struct proxy_ctx_s { unsigned char rbuf[1024]; int rlen; int want; - proxy_established_cb on_established; } proxy_ctx_t; proxy_ctx_t* proxy_ctx_new(const proxy_setting_t* setting); @@ -27,9 +24,10 @@ bool proxy_setting_valid(const proxy_setting_t* setting, bool need_target); // written, or a negative value when the buffer is insufficient. int http_connect_build_request(const proxy_ctx_t* proxy, char* buf, int bufsize); -void proxy_handshake_start(hio_t* io, proxy_established_cb on_established); +void proxy_handshake_start(hio_t* io); +void proxy_handshake_read(hio_t* io); void proxy_handshake_fail(hio_t* io); -int proxy_handshake_send(hio_t* io, const void* buf, int len); +int proxy_handshake_write(hio_t* io, const void* buf, int len); void proxy_handshake_established(hio_t* io); #endif // HV_PROXY_H_ diff --git a/event/socks5.c b/event/socks5.c index 47c70d949..91c8a8e8a 100644 --- a/event/socks5.c +++ b/event/socks5.c @@ -75,6 +75,10 @@ static void socks5_server_upstream_connect(hio_t* upstream) { hio_setcb_read(upstream, hio_write_upstream); hio_setcb_close(io, socks5_server_close); hio_setcb_close(upstream, socks5_server_close); + if (io->proxy) { + proxy_ctx_free(io->proxy); + io->proxy = NULL; + } hio_read(io); hio_read(upstream); } @@ -210,7 +214,7 @@ typedef enum { S5C_RECV_REPLY_ADDR, S5C_RECV_REPLY_DADDR, } socks5_client_state_e; -static void socks5_client_handshake(hio_t* io); +void socks5_client_handshake_read(hio_t* io); static void socks5_client_expect(hio_t* io, int state, int want) { proxy_ctx_t* proxy = io->proxy; @@ -222,7 +226,7 @@ static void socks5_client_expect(hio_t* io, int state, int want) { static void socks5_client_send_connect(hio_t* io) { unsigned char buf[300]; int n = socks5_build_connect_request(io->proxy, buf); - if (n < 0 || proxy_handshake_send(io, buf, n) != 0) { + if (n < 0 || proxy_handshake_write(io, buf, n) != 0) { proxy_handshake_fail(io); return; } @@ -241,7 +245,7 @@ static void socks5_client_dispatch(hio_t* io) { } else if (buf[1] == SOCKS5_AUTH_USERPASS && proxy->setting.username[0]) { unsigned char req[640]; int n = socks5_build_auth_request(proxy, req); - if (proxy_handshake_send(io, req, n) != 0) { proxy_handshake_fail(io); return; } + if (proxy_handshake_write(io, req, n) != 0) { proxy_handshake_fail(io); return; } socks5_client_expect(io, S5C_RECV_AUTH, 2); } else { proxy_handshake_fail(io); @@ -287,7 +291,7 @@ static void socks5_client_dispatch(hio_t* io) { } } -static void socks5_client_handshake(hio_t* io) { +void socks5_client_handshake_read(hio_t* io) { proxy_ctx_t* proxy = io->proxy; while (proxy->rlen < proxy->want) { int need = proxy->want - proxy->rlen; @@ -309,9 +313,9 @@ static void socks5_client_handshake(hio_t* io) { void socks5_client_handshake_start(hio_t* io) { unsigned char buf[8]; int n = socks5_build_method_request(io->proxy, buf); - if (proxy_handshake_send(io, buf, n) != 0) { proxy_handshake_fail(io); return; } + if (proxy_handshake_write(io, buf, n) != 0) { proxy_handshake_fail(io); return; } socks5_client_expect(io, S5C_RECV_METHOD, 2); - hio_add(io, socks5_client_handshake, HV_READ); + hio_add(io, NULL, HV_READ); } // Build the SOCKS5 method-selection request. diff --git a/event/socks5.h b/event/socks5.h index 641410402..f00d29396 100644 --- a/event/socks5.h +++ b/event/socks5.h @@ -36,6 +36,7 @@ int socks5_build_auth_request (const proxy_ctx_t* s5, unsigned char* buf); int socks5_build_connect_request(const proxy_ctx_t* s5, unsigned char* buf); void socks5_client_handshake_start(hio_t* io); +void socks5_client_handshake_read(hio_t* io); END_EXTERN_C diff --git a/event/tls.c b/event/tls.c new file mode 100644 index 000000000..23fba18f8 --- /dev/null +++ b/event/tls.c @@ -0,0 +1,104 @@ +#include "tls.h" + +#include "hevent.h" +#include "herr.h" +#include "hlog.h" +#include "hsocket.h" +#include "proxy.h" + +static int tls_prepare(hio_t* io, hssl_ctx_t ssl_ctx) { + if (io->ssl == NULL) { + if (ssl_ctx == NULL) { + io->error = ERR_NEW_SSL_CTX; + hio_close(io); + return -1; + } + io->ssl = hssl_new(ssl_ctx, io->fd); + if (io->ssl == NULL) { + io->error = ERR_NEW_SSL; + hio_close(io); + return -1; + } + } + hio_enable_ssl(io); + return 0; +} + +static hssl_ctx_t tls_client_ctx(hio_t* io) { + if (io->ssl_ctx) return io->ssl_ctx; + if (g_ssl_ctx) return g_ssl_ctx; + io->ssl_ctx = hssl_ctx_new(NULL); + if (io->ssl_ctx) io->alloced_ssl_ctx = 1; + return io->ssl_ctx; +} + +int tls_server_handshake_start(hio_t* listenio, hio_t* connio) { + hssl_ctx_t ssl_ctx = NULL; + if (connio->ssl == NULL) { + ssl_ctx = listenio->ssl_ctx; + if (ssl_ctx == NULL) { + ssl_ctx = g_ssl_ctx; + } + if (ssl_ctx == NULL) { + listenio->ssl_ctx = ssl_ctx = hssl_ctx_new(NULL); + if (ssl_ctx) listenio->alloced_ssl_ctx = 1; + } + } + if (tls_prepare(connio, ssl_ctx) != 0) return -1; + connio->phase = HIO_PHASE_TLS_SERVER_HANDSHAKING; + tls_handshake_step(connio); + return connio->closed ? -1 : 0; +} + +int tls_client_handshake_start(hio_t* io) { + hssl_ctx_t ssl_ctx = io->ssl == NULL ? tls_client_ctx(io) : NULL; + if (tls_prepare(io, ssl_ctx) != 0) return -1; + + const char* sni = NULL; + if (io->proxy && io->proxy->setting.target_host[0] && !is_ipaddr(io->proxy->setting.target_host)) { + sni = io->proxy->setting.target_host; + } else if (io->hostname && !is_ipaddr(io->hostname)) { + sni = io->hostname; + } + if (sni) { + hssl_set_sni_hostname(io->ssl, sni); + } + + io->phase = HIO_PHASE_TLS_CLIENT_HANDSHAKING; + tls_handshake_step(io); + return io->closed ? -1 : 0; +} + +void tls_handshake_step(hio_t* io) { + bool server = io->phase == HIO_PHASE_TLS_SERVER_HANDSHAKING; + printd("tls %s handshake...\n", server ? "server" : "client"); + int ret = server ? hssl_accept(io->ssl) : hssl_connect(io->ssl); + if (ret == HSSL_OK) { + hio_del(io, HV_RDWR); + io->phase = HIO_PHASE_TLS_ESTABLISHED; + printd("tls handshake finished.\n"); + return; + } + if (ret == HSSL_WANT_READ) { + if (io->events & HV_WRITE) { + hio_del(io, HV_WRITE); + } + if ((io->events & HV_READ) == 0) { + hio_add(io, NULL, HV_READ); + } + return; + } + if (ret == HSSL_WANT_WRITE) { + if (io->events & HV_READ) { + hio_del(io, HV_READ); + } + if ((io->events & HV_WRITE) == 0) { + hio_add(io, NULL, HV_WRITE); + } + return; + } + + hloge("tls %s handshake failed: %d", server ? "server" : "client", ret); + io->error = ERR_SSL_HANDSHAKE; + hio_close(io); +} diff --git a/event/tls.h b/event/tls.h new file mode 100644 index 000000000..3a63ce2e3 --- /dev/null +++ b/event/tls.h @@ -0,0 +1,14 @@ +#ifndef HV_EVENT_TLS_H_ +#define HV_EVENT_TLS_H_ + +#include "hloop.h" + +// Internal event-loop TLS handshake helpers. The caller installs the NIO +// readiness dispatcher before starting a handshake. These functions only +// manage the TLS transport phase and event mask; nio.c owns accept/connect +// completion and user callbacks. +int tls_server_handshake_start(hio_t* listenio, hio_t* connio); +int tls_client_handshake_start(hio_t* io); +void tls_handshake_step(hio_t* io); + +#endif // HV_EVENT_TLS_H_