Changeset

13765:7c57fb2ffbb0 13.0

mod_websocket: Merge session close handling changes from mod_c2s (bug fixes) This should bring some fixes and general robustness that mod_websocket had missed out on. The duplicated code here is not at all ideal. To prevent this happening again, we should figure out how to have the common logic in a single place, while still being able to do the websocket-specific parts that we need. The main known bug that this fixes is that it's possible for a session to get into a non-destroyable state. For example, if we try to session:close() a hibernating session, then session.conn is nil and the function will simply return without doing anything. In the mod_c2s code we already handle this, and just destroy the session. But if a hibernating websocket session is never resumed or becomes non-resumable, it will become immortal! By merging the fix from mod_c2s, the session should now be correctly destroyed.
author Matthew Wild <mwild1@gmail.com>
date Tue, 11 Mar 2025 18:44:40 +0000
parents 13764:667a58e74fd9
children 13766:b11242656300 13767:56fad06904b1
files plugins/mod_websocket.lua
diffstat 1 files changed, 17 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- a/plugins/mod_websocket.lua	Tue Mar 11 18:37:16 2025 +0000
+++ b/plugins/mod_websocket.lua	Tue Mar 11 18:44:40 2025 +0000
@@ -87,6 +87,7 @@
 					end
 				end
 			end
+			stream_error = tostring(stream_error);
 			log("debug", "Disconnecting client, <stream:error> is: %s", stream_error);
 			session.send(stream_error);
 		end
@@ -94,28 +95,33 @@
 		session.send(st.stanza("close", { xmlns = xmlns_framing }));
 		function session.send() return false; end
 
-		-- luacheck: ignore 422/reason
-		-- FIXME reason should be handled in common place
-		local reason = (reason and (reason.name or reason.text or reason.condition)) or reason;
-		session.log("debug", "c2s stream for %s closed: %s", session.full_jid or ("<"..session.ip..">"), reason or "session closed");
+		local reason_text = (reason and (reason.name or reason.text or reason.condition)) or reason;
+		session.log("debug", "c2s stream for %s closed: %s", session.full_jid or session.ip or "<unknown>", reason_text or "session closed");
 
 		-- Authenticated incoming stream may still be sending us stanzas, so wait for </stream:stream> from remote
 		local conn = session.conn;
-		if reason == nil and not session.notopen and session.type == "c2s" then
+		if reason_text == nil and not session.notopen and session.type == "c2s" then
 			-- Grace time to process data from authenticated cleanly-closed stream
 			add_task(stream_close_timeout, function ()
 				if not session.destroyed then
 					session.log("warn", "Failed to receive a stream close response, closing connection anyway...");
-					sm_destroy_session(session, reason);
-					conn:write(build_close(1000, "Stream closed"));
-					conn:close();
+					sm_destroy_session(session, reason_text);
+					if conn then
+						conn:write(build_close(1000, "Stream closed"));
+						conn:close();
+					end
 				end
 			end);
 		else
-			sm_destroy_session(session, reason);
-			conn:write(build_close(1000, "Stream closed"));
-			conn:close();
+			sm_destroy_session(session, reason_text);
+			if conn then
+				conn:write(build_close(1000, "Stream closed"));
+				conn:close();
+			end
 		end
+	else
+		local reason_text = (reason and (reason.name or reason.text or reason.condition)) or reason;
+		sm_destroy_session(session, reason_text);
 	end
 end