From 3ee8cef6d7b53f17949dff3a57e0ca99627ba568 Mon Sep 17 00:00:00 2001 From: Rob Mueller Date: Tue, 4 Nov 2025 00:09:23 -0500 Subject: [PATCH 1/2] feature: implement serversslhandshake method on downstream sockets tcpsock:sslhandshake does a client side ssl handshake for upstream sockets. normally you just specify `ssl` on the listen directive for downstream sockets. however there are certain cases where you want to be able to take a plaintext downstream connection and upgrade it to an ssl encrypted one, such as legacy SMTP STARTTLS this implements a new sock:serversslhandshake method. it uses ssl certificate setup via the existing ssl_certificate and ssl_certificate_key configuration options. it only adds this method to downstream socket connections. --- src/ngx_stream_lua_socket_tcp.c | 267 +++++++++++++++++++++ t/165-serversslhandshake.t | 64 +++++ t/166-serversslhandshake-starttls.t | 203 ++++++++++++++++ t/167-serversslhandshake-errors.t | 353 ++++++++++++++++++++++++++++ 4 files changed, 887 insertions(+) create mode 100644 t/165-serversslhandshake.t create mode 100644 t/166-serversslhandshake-starttls.t create mode 100644 t/167-serversslhandshake-errors.t diff --git a/src/ngx_stream_lua_socket_tcp.c b/src/ngx_stream_lua_socket_tcp.c index f8e9dd85..08ef0d58 100644 --- a/src/ngx_stream_lua_socket_tcp.c +++ b/src/ngx_stream_lua_socket_tcp.c @@ -32,6 +32,7 @@ static int ngx_stream_lua_socket_tcp_bind(lua_State *L); static int ngx_stream_lua_socket_tcp_connect(lua_State *L); #if (NGX_STREAM_SSL) static int ngx_stream_lua_socket_tcp_sslhandshake(lua_State *L); +static int ngx_stream_lua_socket_tcp_serversslhandshake(lua_State *L); #endif static int ngx_stream_lua_socket_tcp_receive(lua_State *L); static int ngx_stream_lua_socket_tcp_receiveany(lua_State *L); @@ -181,6 +182,12 @@ static int ngx_stream_lua_ssl_handshake_retval_handler( ngx_stream_lua_request_t *r, ngx_stream_lua_socket_tcp_upstream_t *u, lua_State *L); static void ngx_stream_lua_ssl_handshake_handler(ngx_connection_t *c); +static int ngx_stream_lua_server_ssl_handshake_retval_handler( + ngx_stream_lua_request_t *r, ngx_stream_lua_socket_tcp_upstream_t *u, + lua_State *L); +static void ngx_stream_lua_ssl_handshake_session_info(ngx_connection_t *c, + lua_State *L); +static void ngx_stream_lua_server_ssl_handshake_handler(ngx_connection_t *c); static int ngx_stream_lua_ssl_free_session(lua_State *L); #endif static void ngx_stream_lua_socket_tcp_close_connection(ngx_connection_t *c); @@ -327,6 +334,13 @@ ngx_stream_lua_inject_socket_tcp_api(ngx_log_t *log, lua_State *L) lua_pushcfunction(L, ngx_stream_lua_socket_tcp_shutdown); lua_setfield(L, -2, "shutdown"); +#if (NGX_STREAM_SSL) + + lua_pushcfunction(L, ngx_stream_lua_socket_tcp_serversslhandshake); + lua_setfield(L, -2, "serversslhandshake"); + +#endif + lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); @@ -2036,6 +2050,259 @@ ngx_stream_lua_ssl_handshake_retval_handler(ngx_stream_lua_request_t *r, return 1; } + +static int +ngx_stream_lua_socket_tcp_serversslhandshake(lua_State *L) +{ + int n, top; + ngx_int_t rc; + ngx_connection_t *c; + + ngx_stream_lua_request_t *r; + ngx_stream_lua_ctx_t *ctx; + ngx_stream_lua_co_ctx_t *coctx; + ngx_stream_lua_socket_tcp_upstream_t *u; + ngx_stream_ssl_srv_conf_t *sscf; + + /* Lua function arguments: self */ + + n = lua_gettop(L); + if (n != 1) { + return luaL_error(L, "ngx.socket serversslhandshake: expecting 1 " + "argument (the object), but seen %d", n); + } + + r = ngx_stream_lua_get_req(L); + if (r == NULL) { + return luaL_error(L, "no request found"); + } + + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, r->connection->log, 0, + "stream lua tcp socket server ssl handshake"); + + luaL_checktype(L, 1, LUA_TTABLE); + + lua_rawgeti(L, 1, SOCKET_CTX_INDEX); + u = lua_touserdata(L, -1); + + if (u == NULL + || u->peer.connection == NULL + || u->read_closed + || u->write_closed) + { + lua_pushnil(L); + lua_pushliteral(L, "closed"); + return 2; + } + + if (u->request != r) { + return luaL_error(L, "bad request"); + } + + ngx_stream_lua_socket_check_busy_connecting(r, u, L); + ngx_stream_lua_socket_check_busy_reading(r, u, L); + ngx_stream_lua_socket_check_busy_writing(r, u, L); + + if (!u->raw_downstream && !u->body_downstream) { + lua_pushnil(L); + lua_pushliteral(L, "only supported for downstream socket"); + return 2; + } + + /* For downstream sockets, the connection is r->connection */ + c = r->connection; + + if (c->ssl && c->ssl->handshaked) { + /* SSL handshake already completed, return session info */ + ngx_stream_lua_ssl_handshake_session_info(c, L); + return 1; + } + + sscf = ngx_stream_get_module_srv_conf(r->session, ngx_stream_ssl_module); + + if (sscf == NULL || sscf->ssl.ctx == NULL) { + lua_pushnil(L); + lua_pushliteral(L, "ssl not configured for this server"); + return 2; + } + + if (ngx_ssl_create_connection(&sscf->ssl, c, NGX_SSL_BUFFER) != NGX_OK) { + lua_pushnil(L); + lua_pushliteral(L, "failed to create ssl connection"); + return 2; + } + + ctx = ngx_stream_lua_get_module_ctx(r, ngx_stream_lua_module); + if (ctx == NULL) { + return luaL_error(L, "no ctx found"); + } + + coctx = ctx->cur_co_ctx; + + c->sendfile = 0; + + u->write_co_ctx = coctx; + + rc = ngx_ssl_handshake(c); + + dd("ngx_ssl_handshake returned %d", (int) rc); + + if (rc == NGX_AGAIN) { + if (c->write->timer_set) { + ngx_del_timer(c->write); + } + + ngx_add_timer(c->read, u->read_timeout); + + u->conn_waiting = 1; + u->write_prepare_retvals = ngx_stream_lua_server_ssl_handshake_retval_handler; + + ngx_stream_lua_cleanup_pending_operation(coctx); + coctx->cleanup = ngx_stream_lua_coctx_cleanup; + coctx->data = u; + + c->ssl->handler = ngx_stream_lua_server_ssl_handshake_handler; + + if (ctx->entered_content_phase) { + r->write_event_handler = ngx_stream_lua_content_wev_handler; + + } else { + r->write_event_handler = ngx_stream_lua_core_run_phases; + } + + return lua_yield(L, 0); + } + + top = lua_gettop(L); + ngx_stream_lua_server_ssl_handshake_handler(c); + return lua_gettop(L) - top; +} + + +static void +ngx_stream_lua_server_ssl_handshake_handler(ngx_connection_t *c) +{ + int waiting; + lua_State *L; + ngx_stream_lua_request_t *r; + ngx_stream_session_t *s; + + ngx_stream_lua_ctx_t *ctx; + ngx_stream_lua_socket_tcp_upstream_t *u; + + /* For downstream sockets, c->data points to the session. */ + s = c->data; + + /* Get the context from the session */ + ctx = ngx_stream_get_module_ctx(s, ngx_stream_lua_module); + if (ctx == NULL) { + return; + } + + r = ctx->request; + /* For downstream sockets, u is stored in ctx->downstream */ + u = ctx->downstream; + + c->read->handler = ngx_stream_lua_request_handler; + c->write->handler = ngx_stream_lua_request_handler; + + waiting = u->conn_waiting; + + L = u->write_co_ctx->co; + + if (c->read->timedout) { + lua_pushnil(L); + lua_pushliteral(L, "timeout"); + goto failed; + } + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + r = u->request; + if (r == NULL) { + return; + } + + if (c->ssl->handshaked) { + if (waiting) { + ngx_stream_lua_socket_handle_conn_success(r, u); + + } else { + (void) ngx_stream_lua_server_ssl_handshake_retval_handler(r, u, L); + } + + return; + } + + lua_pushnil(L); + lua_pushliteral(L, "handshake failed"); + +failed: + + if (waiting) { + ngx_stream_lua_socket_handle_conn_error(r, u, + NGX_STREAM_LUA_SOCKET_FT_SSL); + + } else { + (void) ngx_stream_lua_socket_write_error_retval_handler(r, u, L); + } +} + + +static void +ngx_stream_lua_ssl_handshake_session_info(ngx_connection_t *c, lua_State *L) +{ + const char *protocol; + SSL_CIPHER *cipher; + const char *cipher_name; + + /* Create a table with SSL session information */ + lua_createtable(L, 0, 3); + + /* Add protocol version */ + protocol = SSL_get_version(c->ssl->connection); + if (protocol) { + lua_pushstring(L, protocol); + lua_setfield(L, -2, "protocol"); + } + + /* Add cipher name */ + cipher = (SSL_CIPHER *) SSL_get_current_cipher(c->ssl->connection); + if (cipher) { + cipher_name = SSL_CIPHER_get_name(cipher); + if (cipher_name) { + lua_pushstring(L, cipher_name); + lua_setfield(L, -2, "cipher"); + } + } + + /* Add session reused flag */ + lua_pushboolean(L, SSL_session_reused(c->ssl->connection)); + lua_setfield(L, -2, "session_reused"); +} + + +static int +ngx_stream_lua_server_ssl_handshake_retval_handler(ngx_stream_lua_request_t *r, + ngx_stream_lua_socket_tcp_upstream_t *u, lua_State *L) +{ + ngx_connection_t *c; + + /* Check if an error occurred during the handshake */ + if (u->ft_type) { + return ngx_stream_lua_socket_conn_error_retval_handler(r, u, L); + } + + /* For downstream sockets, the connection is r->connection */ + c = r->connection; + + ngx_stream_lua_ssl_handshake_session_info(c, L); + + return 1; +} + #endif /* NGX_STREAM_SSL */ diff --git a/t/165-serversslhandshake.t b/t/165-serversslhandshake.t new file mode 100644 index 00000000..ce4b7b51 --- /dev/null +++ b/t/165-serversslhandshake.t @@ -0,0 +1,64 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua::Stream; + +repeat_each(2); + +plan tests => repeat_each() * 7; + +#log_level 'warn'; +log_level 'debug'; + +no_long_string(); +#no_diff(); + +run_tests(); + +__DATA__ + +=== TEST 1: serversslhandshake without SSL configured should fail +--- stream_server_config + content_by_lua_block { + local sock, err = ngx.req.socket(true) + if not sock then + ngx.say("failed to get socket: ", err) + return + end + + -- Consume the initial test line + local line, err = sock:receive() + if not line then + ngx.say("failed to receive: ", err) + return + end + + ngx.say("method exists: ", type(sock.serversslhandshake) == "function") + + local session, err = sock:serversslhandshake() + ngx.say("error: ", err or "unexpected success") + } + +--- stream_request +test +--- stream_response +method exists: true +error: ssl not configured for this server +--- no_error_log +[alert] + + + +=== TEST 2: serversslhandshake method doesn't exist on non-downstream socket +--- stream_server_config + content_by_lua_block { + local sock = ngx.socket.tcp() + ngx.say("method exists: ", type(sock.serversslhandshake) == "function") + } + +--- stream_response +method exists: false +--- no_error_log +[error] +[alert] + + diff --git a/t/166-serversslhandshake-starttls.t b/t/166-serversslhandshake-starttls.t new file mode 100644 index 00000000..e08918fb --- /dev/null +++ b/t/166-serversslhandshake-starttls.t @@ -0,0 +1,203 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua::Stream; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3 + 9); + +#log_level 'warn'; +log_level 'debug'; + +no_long_string(); +#no_diff(); + +run_tests(); + +__DATA__ + +=== TEST 1: STARTTLS - plaintext to SSL upgrade +--- stream_config + server { + listen $TEST_NGINX_RAND_PORT_1; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + content_by_lua_block { + local sock = assert(ngx.req.socket(true)) + + -- Send plaintext banner + sock:send("220 Ready\n") + + -- Get STARTTLS command + local data = sock:receive() + if data == "STARTTLS" then + sock:send("220 Go ahead\n") + + -- Perform server-side SSL handshake + local session, err = sock:serversslhandshake() + if not session then + ngx.log(ngx.ERR, "SSL handshake failed: ", err) + return + end + + -- Log session info + ngx.log(ngx.INFO, "SSL session protocol=", session.protocol); + ngx.log(ngx.INFO, "SSL session cipher=", session.cipher); + ngx.log(ngx.INFO, "SSL session session_reused=", session.session_reused); + + -- Now encrypted + data = sock:receive() + if data == "PING" then + sock:send("PONG\n") + end + end + } + } + +--- stream_server_config + content_by_lua_block { + do + local sock = ngx.socket.tcp() + sock:settimeout(3000) + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_RAND_PORT_1) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + -- Get plaintext banner + local line, err = sock:receive() + ngx.say("banner: ", line) + + -- Send STARTTLS + sock:send("STARTTLS\n") + line, err = sock:receive() + ngx.say("response: ", line) + + -- Client SSL handshake (disable session reuse and SNI) + local session, err = sock:sslhandshake(false, nil, false) + if not session then + ngx.say("handshake failed: ", err) + return + end + + ngx.say("handshake: success") + + -- Send encrypted + sock:send("PING\n") + line, err = sock:receive() + ngx.say("encrypted: ", line) + + sock:close() + end + collectgarbage() + } + +--- stream_response +connected: 1 +banner: 220 Ready +response: 220 Go ahead +handshake: success +encrypted: PONG + +--- error_log +SSL session protocol=TLS +SSL session cipher= +SSL session session_reused=false +--- no_error_log +[error] +[alert] + + + +=== TEST 2: STARTTLS - timeout during handshake +--- stream_config + server { + listen $TEST_NGINX_RAND_PORT_1; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + content_by_lua_block { + local sock = assert(ngx.req.socket(true)) + + -- Send plaintext banner + sock:send("220 Ready\n") + + -- Get STARTTLS command + local data = sock:receive() + if data == "STARTTLS" then + sock:send("220 Go ahead\n") + + -- Perform server-side SSL handshake with timeout + sock:settimeout(100) -- 100ms timeout + ngx.log(ngx.INFO, "calling serversslhandshake with 100ms timeout") + local session, err = sock:serversslhandshake() + + if not session then + ngx.log(ngx.WARN, "SSL handshake failed as expected: ", err) + if err == "timeout" then + ngx.log(ngx.WARN, "confirmed: got timeout error") + end + else + ngx.log(ngx.ERR, "unexpected: handshake succeeded") + end + end + sock:close() + } + } + +--- stream_server_config + content_by_lua_block { + local sock = ngx.socket.tcp() + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_RAND_PORT_1) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + -- Get plaintext banner + local line, err = sock:receive() + ngx.say("banner: ", line) + + -- Send STARTTLS + sock:send("STARTTLS\n") + line, err = sock:receive() + ngx.say("response: ", line) + + -- Client pauses instead of doing SSL handshake + ngx.log(ngx.INFO, "client pausing for 1 second instead of handshaking") + ngx.sleep(1) + ngx.log(ngx.INFO, "client done sleeping") + + -- Connection should be closed by server + line, err = sock:receive() + ngx.say("client got receive error: " .. err) + + -- Connection will be closed by server after timeout + ngx.say("client finished") + + sock:close() + } + +--- stream_response +connected: 1 +banner: 220 Ready +response: 220 Go ahead +client got receive error: closed +client finished + +--- error_log +calling serversslhandshake with 100ms timeout +SSL handshake failed as expected: timeout +confirmed: got timeout error +client pausing for 1 second instead of handshaking +client done sleeping + +--- no_error_log +[alert] diff --git a/t/167-serversslhandshake-errors.t b/t/167-serversslhandshake-errors.t new file mode 100644 index 00000000..9e98d692 --- /dev/null +++ b/t/167-serversslhandshake-errors.t @@ -0,0 +1,353 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua::Stream; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4 + 25); + +#log_level 'warn'; +log_level 'debug'; + +no_long_string(); +#no_diff(); + +run_tests(); + +__DATA__ + +=== TEST 1: serversslhandshake - error handling without SSL configured +--- stream_server_config + content_by_lua_block { + local sock = assert(ngx.req.socket(true)) + sock:settimeout(3000) + ngx.log(ngx.INFO, "received socket") + + -- Consume the initial test line + local line, err = sock:receive() + if not line then + ngx.log(ngx.ERR, "failed to receive: ", err) + return + end + ngx.log(ngx.INFO, "received initial line: ", line) + + -- Try to do SSL handshake without SSL configured (should fail) + ngx.log(ngx.INFO, "calling serversslhandshake without SSL") + local session, err = sock:serversslhandshake() + ngx.log(ngx.INFO, "serversslhandshake returned") + + if not session then + ngx.log(ngx.WARN, "serversslhandshake failed: ", err) + if err == "ssl not configured for this server" then + ngx.log(ngx.WARN, "confirmed: correct error for no SSL config") + end + ngx.say("handshake failed: ", err) + else + ngx.log(ngx.ERR, "unexpected: handshake succeeded") + ngx.say("handshake succeeded") + end + } + +--- stream_request +test +--- stream_response +handshake failed: ssl not configured for this server +--- error_log +received socket +received initial line: test +calling serversslhandshake without SSL +serversslhandshake returned +serversslhandshake failed: ssl not configured for this server +confirmed: correct error for no SSL config +--- no_error_log +[alert] +unexpected: handshake succeeded + + + +=== TEST 2: serversslhandshake - client aborts during handshake +--- stream_config + server { + listen $TEST_NGINX_RAND_PORT_1; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + content_by_lua_block { + local sock = assert(ngx.req.socket(true)) + sock:settimeout(3000) + ngx.log(ngx.INFO, "received socket") + + -- Consume the initial test line + local line, err = sock:receive() + if not line then + ngx.log(ngx.ERR, "failed to receive: ", err) + return + end + ngx.log(ngx.INFO, "received initial line: ", line) + + -- Send response line + sock:send("ok\n") + ngx.log(ngx.INFO, "sent ok response") + + ngx.log(ngx.INFO, "calling serversslhandshake") + local session, err = sock:serversslhandshake() + ngx.log(ngx.INFO, "serversslhandshake returned") + + if not session then + ngx.log(ngx.WARN, "handshake failed: ", err) + ngx.say("handshake failed: ", err) + else + ngx.log(ngx.ERR, "unexpected handshake success") + ngx.say("handshake success") + end + } + } + +--- stream_server_config + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(3000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_RAND_PORT_1) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + -- Send initial test line + sock:send("test\n"); + + -- Receive ok response + local line, err = sock:receive() + if not line then + ngx.say("failed to receive ok response: ", err) + return + end + + -- Send partial SSL handshake (Client Hello header only) + sock:send("\x16\x03\x01\x00\x05") + + -- Immediately close without completing handshake + sock:close() + ngx.say("client aborted") + } + +--- stream_response +connected: 1 +client aborted + +--- error_log +received socket +received initial line: test +sent ok response +calling serversslhandshake +serversslhandshake returned +handshake failed: handshake failed + +--- no_error_log +[alert] +unexpected handshake success + + + +=== TEST 3: serversslhandshake - immediate close after successful handshake +--- stream_config + server { + listen $TEST_NGINX_RAND_PORT_1; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + content_by_lua_block { + local sock = assert(ngx.req.socket(true)) + sock:settimeout(3000) + ngx.log(ngx.INFO, "received socket") + + -- Consume the initial test line + local line, err = sock:receive() + if not line then + ngx.log(ngx.ERR, "failed to receive: ", err) + return + end + ngx.log(ngx.INFO, "received initial line: ", line) + + -- Send response line + sock:send("ok\n") + ngx.log(ngx.INFO, "sent ok response") + + ngx.log(ngx.INFO, "calling serversslhandshake") + local session, err = sock:serversslhandshake() + + if not session then + ngx.log(ngx.ERR, "handshake failed: ", err) + ngx.say("handshake failed: ", err) + return + end + + ngx.log(ngx.INFO, "handshake success, protocol: ", session.protocol) + ngx.say("handshake success") + + -- Try to read after handshake + sock:settimeout(1000) + ngx.log(ngx.INFO, "attempting read after handshake") + local line, err = sock:receive() + if not line then + ngx.log(ngx.WARN, "read failed (expected): ", err) + ngx.say("read after handshake: ", err) + else + ngx.log(ngx.INFO, "read line: ", line) + ngx.say("read: ", line) + end + } + } + +--- stream_server_config + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(3000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_RAND_PORT_1) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + -- Send initial test line + sock:send("test\n"); + + -- Receive ok response + local line, err = sock:receive() + if not line then + ngx.say("failed to receive ok response: ", err) + return + end + + -- Perform client SSL handshake + local session, err = sock:sslhandshake(false, nil, false) + if not session then + ngx.say("client handshake failed: ", err) + return + end + + ngx.say("client handshake: success") + + -- Immediately close after handshake + sock:close() + ngx.say("client closed immediately") + } + +--- stream_response +connected: 1 +client handshake: success +client closed immediately + +--- error_log +received socket +received initial line: test +sent ok response +calling serversslhandshake +handshake success, protocol: TLS +attempting read after handshake +read failed (expected): closed + +--- no_error_log +[alert] +handshake failed: + + + +=== TEST 4: serversslhandshake - multiple calls should handle gracefully +--- stream_config + server { + listen $TEST_NGINX_RAND_PORT_1; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + content_by_lua_block { + local sock = assert(ngx.req.socket(true)) + sock:settimeout(3000) + + ngx.log(ngx.INFO, "calling first serversslhandshake") + -- First handshake + local session1, err = sock:serversslhandshake() + if not session1 then + ngx.log(ngx.ERR, "first handshake failed: ", err) + return + end + + ngx.log(ngx.INFO, "first handshake success, protocol: ", session1.protocol) + + -- Try second handshake (should return existing session) + ngx.log(ngx.INFO, "calling second serversslhandshake") + local session2, err = sock:serversslhandshake() + if not session2 then + ngx.log(ngx.ERR, "second handshake failed: ", err) + return + end + + ngx.log(ngx.INFO, "second handshake success, same protocol: ", + tostring(session1.protocol == session2.protocol)) + + -- Verify we can still communicate + ngx.log(ngx.INFO, "attempting communication after multiple handshakes") + local line, err = sock:receive() + if line then + ngx.log(ngx.INFO, "received line, sending response") + sock:send(line:upper() .. "\n") + else + ngx.log(ngx.WARN, "failed to receive: ", err) + end + } + } + +--- stream_server_config + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(3000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_RAND_PORT_1) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + -- Client handshake + local session, err = sock:sslhandshake(false, nil, false) + if not session then + ngx.say("client handshake failed: ", err) + return + end + + ngx.say("client handshake: success") + + -- Send test line + sock:send("hello\n") + local line, err = sock:receive() + ngx.say("response: ", line) + + sock:close() + } + +--- stream_response +connected: 1 +client handshake: success +response: HELLO + +--- error_log +calling first serversslhandshake +first handshake success, protocol: TLS +calling second serversslhandshake +second handshake success, same protocol: true +attempting communication after multiple handshakes +received line, sending response + +--- no_error_log +[error] +[alert] + + From 7afb07cb4dcade79bce56975c39d37fec2a98038 Mon Sep 17 00:00:00 2001 From: lijunlong Date: Fri, 5 Dec 2025 21:52:41 +0800 Subject: [PATCH 2/2] small edits. --- src/ngx_stream_lua_socket_tcp.c | 3 ++- t/165-serversslhandshake.t | 2 -- t/167-serversslhandshake-errors.t | 2 -- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/ngx_stream_lua_socket_tcp.c b/src/ngx_stream_lua_socket_tcp.c index 08ef0d58..13407342 100644 --- a/src/ngx_stream_lua_socket_tcp.c +++ b/src/ngx_stream_lua_socket_tcp.c @@ -2155,7 +2155,8 @@ ngx_stream_lua_socket_tcp_serversslhandshake(lua_State *L) ngx_add_timer(c->read, u->read_timeout); u->conn_waiting = 1; - u->write_prepare_retvals = ngx_stream_lua_server_ssl_handshake_retval_handler; + u->write_prepare_retvals + = ngx_stream_lua_server_ssl_handshake_retval_handler; ngx_stream_lua_cleanup_pending_operation(coctx); coctx->cleanup = ngx_stream_lua_coctx_cleanup; diff --git a/t/165-serversslhandshake.t b/t/165-serversslhandshake.t index ce4b7b51..a25db63c 100644 --- a/t/165-serversslhandshake.t +++ b/t/165-serversslhandshake.t @@ -60,5 +60,3 @@ method exists: false --- no_error_log [error] [alert] - - diff --git a/t/167-serversslhandshake-errors.t b/t/167-serversslhandshake-errors.t index 9e98d692..d6957eea 100644 --- a/t/167-serversslhandshake-errors.t +++ b/t/167-serversslhandshake-errors.t @@ -349,5 +349,3 @@ received line, sending response --- no_error_log [error] [alert] - -