Allow XEP-0198 stream resumption for authenticated anonymous clients
Background
We observed an issue in an Openfire-based Jitsi Meet plugin deployment where the XMPP WebSocket used for Jitsi Meet signaling is disconnected after about two hours.
This is not the JVB / Colibri WebSocket. It is the XMPP signaling WebSocket used by browser participants.
Browser
↓ XMPP WebSocket
Openfire
↓ XMPP
Jicofo / MUC
In enterprise networks, load balancers, reverse proxies, firewalls and WebSocket gateways can have a maximum TCP / WebSocket connection lifetime in addition to idle timeouts.
This kind of maximum connection lifetime cannot be avoided by XMPP ping / pong, because the connection can be closed even while traffic is active.
Problem
Jitsi Meet can recover automatically from a WebSocket disconnect by using XEP-0198 <resume/>, but only when it has received a valid stream resume token.
We confirmed that when stream resumption is available, Jitsi Meet can automatically recover after forcing a WebSocket close with:
APP.conference._room.xmpp.connection.closeWebsocket();
However, Openfire currently does not offer stream resumption to anonymous clients.
This is because StreamManager.allowResume() requires the AuthToken to be non-anonymous:
if (!authToken.isAnonymous()) {
allow = true;
}
As a result, when an anonymous client sends:
<enable xmlns="urn:xmpp:sm:3" resume="true"/>
Openfire does not return:
<enabled xmlns="urn:xmpp:sm:3" resume="true" id="..."/>
Jitsi Meet therefore does not get a resume token and cannot recover automatically after the WebSocket is closed. The temporary transport interruption is handled as a normal XMPP disconnect.
Question
Is rejecting stream resumption for anonymous clients an intentional restriction?
Anonymous clients can already use XEP-0198 acknowledgements:
<r xmlns="urn:xmpp:sm:3"/>
<a xmlns="urn:xmpp:sm:3" h="..."/>
Also, Openfire already appears to contain resume-path logic for anonymous sessions, where the resource from the SM-ID is used to reconstruct the original anonymous full JID.
For long-lived WebSocket clients, rejecting stream resumption only because authToken.isAnonymous() is true seems unnecessarily restrictive.
Proposal
As a first proposal, allow stream resumption for any authenticated client session, whether anonymous or non-anonymous.
private boolean allowResume() {
boolean allow = false;
if (session instanceof ClientSession) {
AuthToken authToken = ((LocalClientSession)session).getAuthToken();
if (authToken != null) {
allow = true;
}
}
return allow;
}
This does not allow stream resumption for unauthenticated sessions.
It only allows stream resumption for client sessions that already have an AuthToken.
Alternative
If enabling this by default for anonymous clients is a concern, an opt-in system property would also work.
stream.management.resume.anonymous=false
if (!authToken.isAnonymous() || ANONYMOUS_RESUME_ENABLED.getValue()) {
allow = true;
}
Expected behavior
Anonymous clients should be able to receive a resumable SM-ID:
<enable xmlns="urn:xmpp:sm:3" resume="true"/>
<enabled xmlns="urn:xmpp:sm:3" resume="true" id="..."/>
After a WebSocket disconnect, the client should be able to resume the stream:
<resume xmlns="urn:xmpp:sm:3" previd="..." h="..."/>
<resumed xmlns="urn:xmpp:sm:3" previd="..." h="..."/>
Test cases
-
Anonymous clients receive
<enabled resume="true" id="..."/>. -
After forcing a WebSocket close with
APP.conference._room.xmpp.connection.closeWebsocket();, Jitsi Meet automatically recovers using<resume/>. -
Stream resumption for non-anonymous clients continues to work as before.
-
Unauthenticated sessions are not allowed to use stream resumption.