use of org.jivesoftware.openfire.session.LocalOutgoingServerSession in project Openfire by igniterealtime.
the class ServerDialback method createOutgoingSession.
/**
* Creates a new connection from the Originating Server to the Receiving Server for
* authenticating the specified domain.
*
* @param localDomain domain of the Originating Server to authenticate with the Receiving Server.
* @param remoteDomain IP address or hostname of the Receiving Server.
* @param port port of the Receiving Server.
* @return an OutgoingServerSession if the domain was authenticated or <tt>null</tt> if none.
*/
public LocalOutgoingServerSession createOutgoingSession(String localDomain, String remoteDomain, int port) {
final Logger log = LoggerFactory.getLogger(Log.getName() + "[Acting as Originating Server: Create Outgoing Session from: " + localDomain + " to RS at: " + remoteDomain + " (port: " + port + ")]");
log.debug("Creating new outgoing session...");
String hostname = null;
int realPort = port;
try {
// Establish a TCP connection to the Receiving Server
final Socket socket = SocketUtil.createSocketToXmppDomain(remoteDomain, port);
if (socket == null) {
log.info("Unable to create new outgoing session: Cannot create a plain socket connection with any applicable remote host.");
return null;
}
connection = new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket, false);
log.debug("Send the stream header and wait for response...");
StringBuilder stream = new StringBuilder();
stream.append("<stream:stream");
stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
stream.append(" xmlns=\"jabber:server\"");
stream.append(" to=\"").append(remoteDomain).append("\"");
stream.append(" from=\"").append(localDomain).append("\"");
stream.append(" xmlns:db=\"jabber:server:dialback\"");
stream.append(">");
connection.deliverRawText(stream.toString());
// Set a read timeout (of 5 seconds) so we don't keep waiting forever
int soTimeout = socket.getSoTimeout();
socket.setSoTimeout(RemoteServerManager.getSocketTimeout());
XMPPPacketReader reader = new XMPPPacketReader();
reader.setXPPFactory(FACTORY);
reader.getXPPParser().setInput(new InputStreamReader(ServerTrafficCounter.wrapInputStream(socket.getInputStream()), CHARSET));
// Get the answer from the Receiving Server
XmlPullParser xpp = reader.getXPPParser();
for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG; ) {
eventType = xpp.next();
}
log.debug("Got a response. Check if the remote server supports dialback...");
if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) {
log.debug("Dialback seems to be supported by the remote server.");
// Restore default timeout
socket.setSoTimeout(soTimeout);
String id = xpp.getAttributeValue("", "id");
OutgoingServerSocketReader socketReader = new OutgoingServerSocketReader(reader);
if (authenticateDomain(socketReader, localDomain, remoteDomain, id)) {
log.debug("Successfully authenticated the connection with dialback.");
// Domain was validated so create a new OutgoingServerSession
StreamID streamID = BasicStreamIDFactory.createStreamID(id);
LocalOutgoingServerSession session = new LocalOutgoingServerSession(localDomain, connection, socketReader, streamID);
connection.init(session);
// Set the hostname as the address of the session
session.setAddress(new JID(null, remoteDomain, null));
log.debug("Successfully created new outgoing session!");
return session;
} else {
log.debug("Failed to authenticate the connection with dialback.");
// Close the connection
connection.close();
}
} else {
log.debug("Error! Invalid namespace in packet: '{}'. Closing connection.", xpp.getText());
// Send an invalid-namespace stream error condition in the response
connection.deliverRawText(new StreamError(StreamError.Condition.invalid_namespace).toXML());
// Close the connection
connection.close();
}
} catch (Exception e) {
log.error("An exception occurred while creating outgoing session to remote server:", e);
// Close the connection
if (connection != null) {
connection.close();
}
}
log.warn("Unable to create a new outgoing session");
return null;
}
use of org.jivesoftware.openfire.session.LocalOutgoingServerSession in project Openfire by igniterealtime.
the class RoutingTableImpl method detectAndFixBrokenCaches.
/**
* When the local node drops out of the cluster (for example, due to a network failure), then from the perspective
* of that node, all other nodes leave the cluster. Under certain circumstances, this can mean that the local node
* no longer has access to all data (or its backups) that is maintained in the clustered caches. From the
* perspective of the remaining node, this data is lost. (OF-2297/OF-2300). To prevent this being an issue, most
* caches have supporting local data structures that maintain a copy of the most critical bits of the data stored in
* the clustered cache. This local copy can be used to detect and/or correct such a loss in data. This is performed
* by this method.
*
* Note that this method is expected to be called as part of {@link #leftCluster(byte[])} only. It will therefor
* mostly restore data that is considered local to the server node, and won't bother with data that's considered
* to be pertinent to other cluster nodes only (as that data will be removed directly after invocation of this
* method anyway).
*
* Note that this method does <em>not</em> process the users sessions cache, as that's a bit of an odd one out. This
* cache is being processed in {@link #restoreUsersSessionsCache()}.
*/
private void detectAndFixBrokenCaches() {
// Ensure that 'serversCache' has content that reflects the locally available s2s connections (we do not need to
// restore the s2s connections on other nodes, as those will be dropped right after invoking this method anyway).
Log.info("Looking for local server routes that have 'dropped out' of the cache (likely as a result of a network failure).");
final Collection<LocalOutgoingServerSession> localServerRoutes = localRoutingTable.getServerRoutes();
final Set<DomainPair> cachesServerRoutes = serversCache.keySet();
final Set<DomainPair> serverRoutesNotInCache = localServerRoutes.stream().map(LocalOutgoingServerSession::getOutgoingDomainPairs).flatMap(Collection::stream).collect(Collectors.toSet());
serverRoutesNotInCache.removeAll(cachesServerRoutes);
if (serverRoutesNotInCache.isEmpty()) {
Log.info("Found no local server routes that are missing from the cache.");
} else {
Log.warn("Found {} server routes that we know locally, but are not (no longer) in the cache. This can occur when a cluster node fails, but should not occur otherwise. Missing server routes: {}", serverRoutesNotInCache.size(), serverRoutesNotInCache.stream().map(DomainPair::toString).collect(Collectors.joining(", ")));
for (final DomainPair missing : serverRoutesNotInCache) {
Log.info("Restoring server route: {}", missing);
serversCache.put(missing, XMPPServer.getInstance().getNodeID());
}
}
// Ensure that 'componentsCache' has content that reflects the locally available components. The component route
// cache is special in the sense that an entry is not directly related to a single cluster node. Therefor we
// need to ensure that all entries are in there, before surgically removing those that really need to be removed.
// Restore cache from 'remote' data structure
Log.info("Looking for and restoring component routes that have 'dropped out' of the cache (likely as a result of a network failure).");
componentsByClusterNode.forEach((key, value) -> {
for (final String componentDomain : value) {
CacheUtil.addValueToMultiValuedCache(componentsCache, componentDomain, key, HashSet::new);
}
});
// Restore cache from 'local' data structure
localRoutingTable.getComponentRoute().forEach(route -> CacheUtil.addValueToMultiValuedCache(componentsCache, route.getAddress().getDomain(), server.getNodeID(), HashSet::new));
// Ensure that 'usersCache' has content that reflects the locally available client connections (we do not need
// to restore the client connections on other nodes, as those will be dropped right after invoking this method anyway).
Log.info("Looking for local (non-anonymous) client routes that have 'dropped out' of the cache (likely as a result of a network failure).");
final Collection<LocalClientSession> localClientRoutes = localRoutingTable.getClientRoutes();
final Map<String, LocalClientSession> localUserRoutes = localClientRoutes.stream().filter(r -> !r.isAnonymousUser()).collect(Collectors.toMap((LocalClientSession localClientSession) -> localClientSession.getAddress().toString(), Function.identity()));
final Set<String> cachedUsersRoutes = usersCache.keySet();
final Set<String> userRoutesNotInCache = localUserRoutes.values().stream().map(LocalClientSession::getAddress).map(JID::toString).collect(Collectors.toSet());
userRoutesNotInCache.removeAll(cachedUsersRoutes);
if (userRoutesNotInCache.isEmpty()) {
Log.info("Found no local (non-anonymous) user routes that are missing from the cache.");
} else {
Log.warn("Found {} (non-anonymous) user routes that we know locally, but are not (no longer) in the cache. This can occur when a cluster node fails, but should not occur otherwise.", userRoutesNotInCache.size());
for (String missing : userRoutesNotInCache) {
Log.info("Restoring (non-anonymous) user routes: {}", missing);
final LocalClientSession localClientSession = localUserRoutes.get(missing);
// We've established this with the filtering above.
assert localClientSession != null;
addClientRoute(localClientSession.getAddress(), localClientSession);
}
}
// Ensure that 'anonymousUsersCache' has content that reflects the locally available client connections (we do not need
// to restore the client connections on other nodes, as those will be dropped right after invoking this method anyway).
Log.info("Looking for local (non-anonymous) client routes that have 'dropped out' of the cache (likely as a result of a network failure).");
final Map<String, LocalClientSession> localAnonymousUserRoutes = localClientRoutes.stream().filter(LocalClientSession::isAnonymousUser).collect(Collectors.toMap((LocalClientSession localClientSession) -> localClientSession.getAddress().toString(), Function.identity()));
final Set<String> cachedAnonymousUsersRoutes = anonymousUsersCache.keySet();
// defensive copy - we should not modify localAnonymousUserRoutes!
final Set<String> anonymousUserRoutesNotInCache = new HashSet<>(localAnonymousUserRoutes.keySet());
anonymousUserRoutesNotInCache.removeAll(cachedAnonymousUsersRoutes);
if (anonymousUserRoutesNotInCache.isEmpty()) {
Log.info("Found no local anonymous user routes that are missing from the cache.");
} else {
Log.warn("Found {} anonymous user routes that we know locally, but are not (no longer) in the cache. This can occur when a cluster node fails, but should not occur otherwise.", anonymousUserRoutesNotInCache.size());
for (String missing : anonymousUserRoutesNotInCache) {
Log.info("Restoring (non-anonymous) user route: {}", missing);
final LocalClientSession localClientSession = localAnonymousUserRoutes.get(missing);
// We've established this with the filtering above.
assert localClientSession != null;
addClientRoute(localClientSession.getAddress(), localClientSession);
}
}
}
use of org.jivesoftware.openfire.session.LocalOutgoingServerSession in project Openfire by igniterealtime.
the class RoutingTableImpl method restoreCacheContent.
/**
* When the local node is joining or leaving a cluster, {@link org.jivesoftware.util.cache.CacheFactory} will swap
* the implementation used to instantiate caches. This causes the cache content to be 'reset': it will no longer
* contain the data that's provided by the local node. This method restores data that's provided by the local node
* in the cache. It is expected to be invoked right after joining ({@link #joinedCluster()} or leaving
* ({@link #leftCluster()} a cluster.
*/
private void restoreCacheContent() {
Log.debug("Restoring cache content for cache '{}' by adding all outgoing server routes that are connected to the local cluster node.", serversCache.getName());
// Check if there are local s2s connections that are already in the cache for remote nodes
Set<DomainPair> localServerRoutesToRemove = new HashSet<>();
localRoutingTable.getServerRoutes().forEach(route -> route.getOutgoingDomainPairs().forEach(address -> {
final Lock lock = serversCache.getLock(address);
lock.lock();
try {
if (serversCache.containsKey(address)) {
Log.info("We have an s2s connection to {}, but this connection also exists on other nodes. They are not allowed to both exist, so this local s2s connection will be terminated.", address);
localServerRoutesToRemove.add(address);
} else {
serversCache.put(address, server.getNodeID());
}
} finally {
lock.unlock();
}
}));
for (DomainPair localServerRouteToRemove : localServerRoutesToRemove) {
final RoutableChannelHandler route = localRoutingTable.getRoute(localServerRouteToRemove);
if (route instanceof LocalOutgoingServerSession) {
// That will result in the s2s connection actually being removed from the LocalRoutingTable.
try {
LocalOutgoingServerSession.class.cast(route).close();
} catch (Exception e) {
Log.warn("Failed to terminate the local s2s connection for " + localServerRouteToRemove + ".", e);
}
} else {
Log.warn("Failed to terminate the local s2s connection for {} because it is a {} instead of a LocalOutgoingServerSession.", localServerRouteToRemove, route.getClass());
}
}
Log.debug("Restoring cache content for cache '{}' by adding all component routes that are connected to the local cluster node.", componentsCache.getName());
localRoutingTable.getComponentRoute().forEach(route -> CacheUtil.addValueToMultiValuedCache(componentsCache, route.getAddress().getDomain(), server.getNodeID(), HashSet::new));
addLocalClientRoutesToCache();
}
Aggregations