Skip to content
297 changes: 149 additions & 148 deletions xmppserver/src/main/java/org/jivesoftware/openfire/RoutingTable.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,48 @@
import java.util.stream.Collectors;

/**
* Manages the sessions associated with an account. The information
* maintained by the Session manager is entirely transient and does
* not need to be preserved between server restarts.
* Manages the sessions associated to XMPP entities connected to Openfire. Such entities include:
*
* <ul>
* <li>Users / client sessions</li>
* <li>Other XMPP domains / server sessions</li>
* <li>(External) XMPP components sessions</li>
* <li>Connection Managers / multiplexer sessions</li>
* </ul>
*
* Sessions relate to routes (as managed by a {@link RoutingTable}), but are different: a <em>session</em> represents
* the connectivity between software entities, whereas a <em>route</em> represents the capability for an entity to be
* sent XMPP data. Although there's much overlap between the two, there are differences:
*
* <ul>
* <li>Not every session represents a entity that can be routed to. A <strong>Connection Manager</strong> (multiplexer),
* for example, is not an XMPP entity. Although multiplexer sessions exists (which represent the connection between
* Openfire and a Connection Manager instance), multiplexer <em>routes</em> do not exist.</li>
*
* <li>Not every route has a corresponding session: A <strong>Internal Component</strong> for example represents an
* addressable XMPP entity, but does not have a Session. An <em>External</em> Component, on the other hand, does have
* an associated session.</li>
*
* <li><strong>Client</strong> sessions are somewhat special in that they can be routed to only when they have sent
* Initial Presence (a variant exists for Client Sessions to be routable only for specific entities, when the session
* has sent those entities Directed Presence).</li>
* <ul>Connections to other XMPP domains (<strong>Server Sessions</strong>) are typically not bidirectional. Instead,
* two types of sessions exist:
*
* <ul>
* <li>An <em>outgoing</em> server session</li>
* <li>An <em>incoming</em> server session</li>
* </ul>
*
* Both types are supported in a SessionManager. As an 'incoming' server session is not used to route stanzas to, only
* 'outgoing' server sessions have routes.
* </li></ul>
*
* The information maintained by the Session manager is entirely transient and does not need to be preserved between
* server restarts.
*
* @author Derek DeMoro
* @see RoutingTable
*/
public class SessionManager extends BasicModule implements ClusterEventListener
{
Expand Down Expand Up @@ -284,7 +321,7 @@ public synchronized void terminateDetached(LocalSession session) {
// OF-1923: Only close the session if it has not been replaced by another session (if the session
// has been replaced, then the condition below will compare to distinct instances). This *should* not
// occur (but has been observed, prior to the fix of OF-1923). This check is left in as a safeguard.
final ClientSession currentSession = routingTable.getClientRoute(session.getAddress());
final ClientSession currentSession = getSession(session.getAddress());
if (session == currentSession) {
try {
if ((clientSession.getPresence().isAvailable() || !clientSession.wasAvailable()) &&
Expand Down Expand Up @@ -1054,14 +1091,10 @@ public List<OutgoingServerSession> getOutgoingServerSessions(String host) {
}

public Collection<ClientSession> getSessions(String username) {
List<ClientSession> sessionList = new ArrayList<>();
if (username != null && serverName != null) {
List<JID> addresses = routingTable.getRoutes(new JID(username, serverName, null, true), null);
for (JID address : addresses) {
sessionList.add(routingTable.getClientRoute(address));
}
return routingTable.getClientRoutes(new JID(username, serverName, null, true));
}
return sessionList;
return new ArrayList<>();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import org.dom4j.Element;
import org.jivesoftware.openfire.IQHandlerInfo;
import org.jivesoftware.openfire.RoutingTable;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.AuthToken;
Expand Down Expand Up @@ -54,7 +53,6 @@ public class IQBindHandler extends IQHandler {

private IQHandlerInfo info;
private String serverName;
private RoutingTable routingTable;

public IQBindHandler() {
super("Resource Binding handler");
Expand Down Expand Up @@ -115,7 +113,7 @@ public IQ handleIQ(IQ packet) throws UnauthorizedException {
// If a session already exists with the requested JID, then check to see
// if we should kick it off or refuse the new connection
final JID desiredJid = new JID(username, serverName, resource, true);
ClientSession oldSession = routingTable.getClientRoute(desiredJid);
ClientSession oldSession = sessionManager.getSession(desiredJid);
if (oldSession != null) {
try {
if (oldSession.isClosed()) {
Expand Down Expand Up @@ -180,7 +178,6 @@ public IQ handleIQ(IQ packet) throws UnauthorizedException {
@Override
public void initialize(XMPPServer server) {
super.initialize(server);
routingTable = server.getRoutingTable();
serverName = server.getServerInfo().getXMPPDomain();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -526,8 +526,9 @@ public void removedExpiredPresences() {
new HashMap<>(localDirectedPresences);
for (Map.Entry<String, Collection<DirectedPresence>> entry : copy.entrySet()) {
for (DirectedPresence directedPresence : entry.getValue()) {
if (!routingTable.hasClientRoute(directedPresence.getHandler()) &&
!routingTable.hasComponentRoute(directedPresence.getHandler())) {
// TODO This appears to remove all directed presence for non-local entities. Determine if that's true, and if so, desirable.
if (sessionManager.getSession(directedPresence.getHandler()) == null &&
sessionManager.getComponentSession(directedPresence.getHandler().getDomain()) == null) {
Collection<DirectedPresence> presences = localDirectedPresences.get(entry.getKey());
presences.remove(directedPresence);
if (presences.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.jivesoftware.openfire.muc.spi;

import org.jivesoftware.openfire.RoutingTable;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.cluster.ClusteredCacheEntryListener;
import org.jivesoftware.openfire.cluster.NodeID;
Expand Down Expand Up @@ -318,7 +319,7 @@ public Set<OccupantManager.Occupant> restoreCacheContentAfterJoin(@Nonnull final
Log.trace("Trying to add occupant '{}' but no role for that occupant exists in the local room. Data inconsistency?", localOccupantToRestore.getRealJID());
continue;
} else {
Log.trace("Found localOccupantRole {} for localOccupantToRestore {}, client route = {}", localOccupant, localOccupantToRestore.getRealJID(), XMPPServer.getInstance().getRoutingTable().getClientRoute(localOccupantToRestore.getRealJID()));
Log.trace("Found localOccupantRole {} for localOccupantToRestore {}, client session = {}", localOccupant, localOccupantToRestore.getRealJID(), SessionManager.getInstance().getSession(localOccupantToRestore.getRealJID()));
}

// OF-2165
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.jivesoftware.openfire.session;

import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.spi.ClientRoute;
import org.jivesoftware.openfire.spi.RoutingTableImpl;
Expand Down Expand Up @@ -54,7 +55,7 @@ public ClientSessionTask(JID address, Operation operation) {

Session getSession() {
if (session == null) {
session = XMPPServer.getInstance().getRoutingTable().getClientRoute(address);
session = SessionManager.getInstance().getSession(address);
}
return session;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2007-2009 Jive Software, 2021 Ignite Realtime Foundation. All rights reserved.
* Copyright (C) 2007-2009 Jive Software, 2021-2025 Ignite Realtime Foundation. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -18,7 +18,6 @@

import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.StreamID;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.spi.BasicStreamIDFactory;
import org.jivesoftware.util.cache.ClusterTask;
import org.jivesoftware.util.cache.ExternalizableUtil;
Expand Down Expand Up @@ -108,7 +107,7 @@ public void readExternal(ObjectInput in) throws IOException {

Session getSession() {
if (sessionType == SessionType.client) {
return XMPPServer.getInstance().getRoutingTable().getClientRoute(address);
return SessionManager.getInstance().getSession(address);
}
else if (sessionType == SessionType.component) {
return SessionManager.getInstance().getComponentSession(address.getDomain());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2007-2009 Jive Software, 2021 Ignite Realtime Foundation. All rights reserved.
* Copyright (C) 2007-2009 Jive Software, 2021-2025 Ignite Realtime Foundation. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -128,7 +128,7 @@ public void readExternal(ObjectInput in) throws IOException {

Session getSession() {
if (sessionType == SessionType.client) {
return XMPPServer.getInstance().getRoutingTable().getClientRoute(address);
return SessionManager.getInstance().getSession(address);
}
else if (sessionType == SessionType.component) {
return SessionManager.getInstance().getComponentSession(address.getDomain());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
* or unavailable the routing table will be updated too.<p>
*
* When running inside of a cluster the routing table will also keep references to routes
* hosted in other cluster nodes. A {@link RemotePacketRouter} will be use to route packets
* hosted in other cluster nodes. A {@link RemotePacketRouter} will be used to route packets
* to routes hosted in other cluster nodes.<p>
*
* Failure to route a packet will end up sending {@link IQRouter#routingFailed(JID, Packet)},
Expand Down Expand Up @@ -818,6 +818,31 @@ public ClientSession getClientRoute(JID jid) {
return session;
}

@Override
public Set<ClientSession> getClientRoutes(JID jid) {
final Set<ClientSession> clientSessions = new HashSet<>();
final Lock lock = usersSessionsCache.getLock(jid.toBareJID());
lock.lock(); // temporarily block new sessions for this JID
try {
Set<String> sessionFullJids = usersSessionsCache.get(jid.toBareJID());
if (sessionFullJids != null) {
for (String sessionFullJid : sessionFullJids) {
ClientRoute clientRoute = usersCache.get(sessionFullJid);
if (clientRoute != null) {
final ClientSession clientSession = getClientRoute(new JID(sessionFullJid));
if (clientSession != null) {
clientSessions.add(clientSession);
}
}
}
}
}
finally {
lock.unlock();
}
return clientSessions;
}

@Override
public Collection<ClientSession> getClientsRoutes(boolean onlyLocal) {
// Add sessions hosted by this cluster node
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,8 @@ private void startResume(String namespace, String previd, long h) {
Log.debug("Resuming session for '{}'. Current session: {}", fullJid, session.getStreamID());

// Locate existing session.
final ClientSession route = XMPPServer.getInstance().getRoutingTable().getClientRoute(fullJid);
if (route == null) {
final ClientSession existingSession = XMPPServer.getInstance().getSessionManager().getSession(fullJid);
if (existingSession == null) {
Log.debug("Not able for client of '{}' to resume a session on this cluster node. No session was found for this client.", fullJid);
if (LOCATION_TERMINATE_OTHERS_ENABLED.getValue()) {
// When the client tries to resume a connection on this host, it is unlikely to try other hosts. Remove any detached sessions living elsewhere in the cluster. (OF-2753)
Expand All @@ -337,7 +337,7 @@ private void startResume(String namespace, String previd, long h) {
return;
}

if (!(route instanceof LocalClientSession)) {
if (!(existingSession instanceof LocalClientSession)) {
Log.debug("Not allowing a client of '{}' to resume a session on this cluster node. The session can only be resumed on the Openfire cluster node where the original session was connected.", fullJid);
if (LOCATION_TERMINATE_OTHERS_ENABLED.getValue()) {
// When the client tries to resume a connection on this host, it is unlikely to try other hosts. Remove any detached sessions living elsewhere in the cluster. (OF-2753)
Expand All @@ -347,15 +347,15 @@ private void startResume(String namespace, String previd, long h) {
return;
}

final LocalClientSession otherSession = (LocalClientSession) route;
final LocalClientSession otherSession = (LocalClientSession) existingSession;
if (!otherSession.getStreamID().getID().equals(streamId)) {
sendError(new PacketError(PacketError.Condition.item_not_found));
return;
}
Log.debug("Found existing session for '{}', checking status", fullJid);

// OF-2811: Cannot resume a session that's already closed. That session is likely busy firing its 'closeListeners'.
if (route.isClosed()) {
if (otherSession.isClosed()) {
Log.debug("Not allowing a client of '{}' to resume a session, as the preexisting session is already in process of being closed.", fullJid);
sendError(new PacketError(PacketError.Condition.unexpected_request));
return;
Expand Down
Loading