Commit 3882ca60 authored by Gaston Dombiak's avatar Gaston Dombiak Committed by gato

Optimized (a lot) logic for starting s2s connections to several remote servers...

Optimized (a lot) logic for starting s2s connections to several remote servers at the same time. JM-657

git-svn-id: http://svn.igniterealtime.org/svn/repos/wildfire/trunk@6360 b35dd754-fafc-0310-a699-88a17e54d16e
parent f7922a53
...@@ -94,7 +94,7 @@ public class OutgoingServerSession extends Session { ...@@ -94,7 +94,7 @@ public class OutgoingServerSession extends Session {
* @param hostname the hostname of the remote server. * @param hostname the hostname of the remote server.
* @return True if the domain was authenticated by the remote server. * @return True if the domain was authenticated by the remote server.
*/ */
public static boolean authenticateDomain(String domain, String hostname) { static boolean authenticateDomain(String domain, String hostname) {
if (hostname == null || hostname.length() == 0 || hostname.trim().indexOf(' ') > -1) { if (hostname == null || hostname.length() == 0 || hostname.trim().indexOf(' ') > -1) {
// Do nothing if the target hostname is empty, null or contains whitespaces // Do nothing if the target hostname is empty, null or contains whitespaces
return false; return false;
...@@ -105,11 +105,12 @@ public class OutgoingServerSession extends Session { ...@@ -105,11 +105,12 @@ public class OutgoingServerSession extends Session {
return false; return false;
} }
OutgoingServerSession session;
// Check if a session, that is using server dialback, already exists to the desired // Check if a session, that is using server dialback, already exists to the desired
// hostname (i.e. remote server). If no one exists then create a new session. The same // hostname (i.e. remote server). If no one exists then create a new session. The same
// session will be used for the same hostname for all the domains to authenticate // session will be used for the same hostname for all the domains to authenticate
SessionManager sessionManager = SessionManager.getInstance(); SessionManager sessionManager = SessionManager.getInstance();
OutgoingServerSession session = sessionManager.getOutgoingServerSession(hostname); session = sessionManager.getOutgoingServerSession(hostname);
if (session == null) { if (session == null) {
// Try locating if the remote server has previously authenticated with this server // Try locating if the remote server has previously authenticated with this server
for (IncomingServerSession incomingSession : sessionManager for (IncomingServerSession incomingSession : sessionManager
...@@ -123,8 +124,7 @@ public class OutgoingServerSession extends Session { ...@@ -123,8 +124,7 @@ public class OutgoingServerSession extends Session {
// session // session
session.addHostname(hostname); session.addHostname(hostname);
break; break;
} } else {
else {
session = null; session = null;
} }
} }
...@@ -134,70 +134,66 @@ public class OutgoingServerSession extends Session { ...@@ -134,70 +134,66 @@ public class OutgoingServerSession extends Session {
if (session == null) { if (session == null) {
int port = RemoteServerManager.getPortForServer(hostname); int port = RemoteServerManager.getPortForServer(hostname);
// No session was found to the remote server so make sure that only one is created // No session was found to the remote server so make sure that only one is created
synchronized (hostname.intern()) { session = sessionManager.getOutgoingServerSession(hostname);
session = sessionManager.getOutgoingServerSession(hostname); if (session == null) {
if (session == null) { session = createOutgoingSession(domain, hostname, port);
session = createOutgoingSession(domain, hostname, port); if (session != null) {
if (session != null) { // Add the new hostname to the list of names that the server may have
// Add the new hostname to the list of names that the server may have session.addHostname(hostname);
session.addHostname(hostname); // Add the validated domain as an authenticated domain
// Add the validated domain as an authenticated domain session.addAuthenticatedDomain(domain);
session.addAuthenticatedDomain(domain); // Notify the SessionManager that a new session has been created
// Notify the SessionManager that a new session has been created sessionManager.outgoingServerSessionCreated(session);
sessionManager.outgoingServerSessionCreated(session); return true;
return true; } else {
// Ensure that the hostname is not an IP address (i.e. contains chars)
if (!pattern.matcher(hostname).find()) {
return false;
} }
else { // Check if hostname is a subdomain of an existing outgoing session
// Ensure that the hostname is not an IP address (i.e. contains chars) for (String otherHost : sessionManager.getOutgoingServers()) {
if (!pattern.matcher(hostname).find()) { if (hostname.contains(otherHost)) {
return false; session = sessionManager.getOutgoingServerSession(otherHost);
// Add the new hostname to the found session
session.addHostname(hostname);
return true;
} }
// Check if hostname is a subdomain of an existing outgoing session }
for (String otherHost : sessionManager.getOutgoingServers()) { // Try to establish a connection to candidate hostnames. Iterate on the
if (hostname.contains(otherHost)) { // substring after the . and try to establish a connection. If a
session = sessionManager.getOutgoingServerSession(otherHost); // connection is established then the same session will be used for
// Add the new hostname to the found session // sending packets to the "candidate hostname" as well as for the
session.addHostname(hostname); // requested hostname (i.e. the subdomain of the candidate hostname)
return true; // This trick is useful when remote servers haven't registered in their
} // DNSs an entry for their subdomains
int index = hostname.indexOf('.');
while (index > -1 && index < hostname.length()) {
String newHostname = hostname.substring(index + 1);
String serverName = XMPPServer.getInstance().getServerInfo()
.getName();
if ("com".equals(newHostname) || "net".equals(newHostname) ||
"org".equals(newHostname) ||
"gov".equals(newHostname) ||
"edu".equals(newHostname) ||
serverName.equals(newHostname)) {
return false;
} }
// Try to establish a connection to candidate hostnames. Iterate on the session = createOutgoingSession(domain, newHostname, port);
// substring after the . and try to establish a connection. If a if (session != null) {
// connection is established then the same session will be used for // Add the new hostname to the list of names that the server may have
// sending packets to the "candidate hostname" as well as for the session.addHostname(hostname);
// requested hostname (i.e. the subdomain of the candidate hostname) // Add the validated domain as an authenticated domain
// This trick is useful when remote servers haven't registered in their session.addAuthenticatedDomain(domain);
// DNSs an entry for their subdomains // Notify the SessionManager that a new session has been created
int index = hostname.indexOf('.'); sessionManager.outgoingServerSessionCreated(session);
while (index > -1 && index < hostname.length()) { // Add the new hostname to the found session
String newHostname = hostname.substring(index + 1); session.addHostname(newHostname);
String serverName = XMPPServer.getInstance().getServerInfo() return true;
.getName(); } else {
if ("com".equals(newHostname) || "net".equals(newHostname) || index = hostname.indexOf('.', index + 1);
"org".equals(newHostname) ||
"gov".equals(newHostname) ||
"edu".equals(newHostname) ||
serverName.equals(newHostname)) {
return false;
}
session = createOutgoingSession(domain, newHostname, port);
if (session != null) {
// Add the new hostname to the list of names that the server may have
session.addHostname(hostname);
// Add the validated domain as an authenticated domain
session.addAuthenticatedDomain(domain);
// Notify the SessionManager that a new session has been created
sessionManager.outgoingServerSessionCreated(session);
// Add the new hostname to the found session
session.addHostname(newHostname);
return true;
}
else {
index = hostname.indexOf('.', index + 1);
}
} }
return false;
} }
return false;
} }
} }
} }
......
...@@ -20,10 +20,10 @@ import org.jivesoftware.wildfire.XMPPServer; ...@@ -20,10 +20,10 @@ import org.jivesoftware.wildfire.XMPPServer;
import org.jivesoftware.wildfire.auth.UnauthorizedException; import org.jivesoftware.wildfire.auth.UnauthorizedException;
import org.xmpp.packet.*; import org.xmpp.packet.*;
import java.util.concurrent.BlockingQueue; import java.util.HashMap;
import java.util.concurrent.LinkedBlockingQueue; import java.util.Map;
import java.util.concurrent.ThreadPoolExecutor; import java.util.Queue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.*;
/** /**
* An OutgoingSessionPromise provides an asynchronic way for sending packets to remote servers. * An OutgoingSessionPromise provides an asynchronic way for sending packets to remote servers.
...@@ -52,6 +52,8 @@ public class OutgoingSessionPromise implements RoutableChannelHandler { ...@@ -52,6 +52,8 @@ public class OutgoingSessionPromise implements RoutableChannelHandler {
*/ */
private ThreadPoolExecutor threadPool; private ThreadPoolExecutor threadPool;
private Map<String, PacketsProcessor> packetsProcessors = new HashMap<String, PacketsProcessor>();
/** /**
* Flag that indicates if the process that consumed the queued packets should stop. * Flag that indicates if the process that consumed the queued packets should stop.
*/ */
...@@ -88,20 +90,25 @@ public class OutgoingSessionPromise implements RoutableChannelHandler { ...@@ -88,20 +90,25 @@ public class OutgoingSessionPromise implements RoutableChannelHandler {
if (threadPool.getActiveCount() < threadPool.getMaximumPoolSize()) { if (threadPool.getActiveCount() < threadPool.getMaximumPoolSize()) {
// Wait until a packet is available // Wait until a packet is available
final Packet packet = packets.take(); final Packet packet = packets.take();
// Process the packet in another thread
threadPool.execute(new Runnable() { boolean newProcessor = false;
public void run() { PacketsProcessor packetsProcessor;
try { String domain = packet.getTo().getDomain();
createSessionAndSendPacket(packet); synchronized (domain.intern()) {
} packetsProcessor = packetsProcessors.get(domain);
catch (Exception e) { if (packetsProcessor == null) {
returnErrorToSender(packet); packetsProcessor =
Log.debug( new PacketsProcessor(OutgoingSessionPromise.this, domain, routingTable);
"Error sending packet to remote server: " + packet, packetsProcessors.put(domain, packetsProcessor);
e); newProcessor = true;
}
} }
}); packetsProcessor.addPacket(packet);
}
if (newProcessor) {
// Process the packet in another thread
threadPool.execute(packetsProcessor);
}
} }
else { else {
// No threads are available so take a nap :) // No threads are available so take a nap :)
...@@ -125,99 +132,154 @@ public class OutgoingSessionPromise implements RoutableChannelHandler { ...@@ -125,99 +132,154 @@ public class OutgoingSessionPromise implements RoutableChannelHandler {
return instance; return instance;
} }
private void createSessionAndSendPacket(Packet packet) throws Exception { /**
// Create a connection to the remote server from the domain where the packet has been sent * Shuts down the thread that consumes the queued packets and also stops the pool
boolean created = OutgoingServerSession * of threads that actually send the packets to the remote servers.
.authenticateDomain(packet.getFrom().getDomain(), packet.getTo().getDomain()); */
if (created) { public void shutdown() {
// A connection to the remote server was created so get the route and send the packet threadPool.shutdown();
ChannelHandler route = routingTable.getRoute(packet.getTo()); shutdown = true;
if (route != null) { }
route.process(packet);
public JID getAddress() {
// TODO Will somebody send this message to me????
return null;
}
public void process(Packet packet) {
// Queue the packet. Another process will process the queued packets.
packets.add(packet.createCopy());
}
private void processorDone(PacketsProcessor packetsProcessor) {
synchronized(packetsProcessor.getDomain().intern()) {
if (packetsProcessor.isDone()) {
packetsProcessors.remove(packetsProcessor.getDomain());
} }
else { else {
throw new Exception("Failed to create connection to remote server"); threadPool.execute(packetsProcessor);
} }
} }
else {
throw new Exception("Failed to create connection to remote server");
}
} }
private void returnErrorToSender(Packet packet) { private static class PacketsProcessor implements Runnable {
XMPPServer server = XMPPServer.getInstance();
JID from = packet.getFrom(); private OutgoingSessionPromise promise;
JID to = packet.getTo(); private String domain;
if (!server.isLocal(from) && !XMPPServer.getInstance().matchesComponent(from) && private RoutingTable routingTable;
!server.isLocal(to) && !XMPPServer.getInstance().matchesComponent(to)) { private Queue<Packet> packets = new ConcurrentLinkedQueue<Packet>();
// Do nothing since the sender and receiver of the packet that failed to reach a remote
// server are not local users. This prevents endless loops if the FROM or TO address public PacketsProcessor(OutgoingSessionPromise promise, String domain, RoutingTable routingTable) {
// are non-existen addresses this.promise = promise;
return; this.domain = domain;
this.routingTable = routingTable;
} }
// TODO Send correct error condition: timeout or not_found depending on the real error public void run() {
try { while (!isDone()) {
if (packet instanceof IQ) { Packet packet = packets.poll();
IQ reply = new IQ(); if (packet != null) {
reply.setID(((IQ) packet).getID()); try {
reply.setTo(from); sendPacket(packet);
reply.setFrom(to); }
reply.setChildElement(((IQ) packet).getChildElement().createCopy()); catch (Exception e) {
reply.setError(PacketError.Condition.remote_server_not_found); returnErrorToSender(packet);
ChannelHandler route = routingTable.getRoute(reply.getTo()); Log.debug(
if (route != null) { "Error sending packet to remote server: " + packet,
route.process(reply); e);
}
} }
} }
else if (packet instanceof Presence) { promise.processorDone(this);
Presence reply = new Presence(); }
reply.setID(packet.getID());
reply.setTo(from); private void sendPacket(Packet packet) throws Exception {
reply.setFrom(to); // Create a connection to the remote server from the domain where the packet has been sent
reply.setError(PacketError.Condition.remote_server_not_found); boolean created = OutgoingServerSession
ChannelHandler route = routingTable.getRoute(reply.getTo()); .authenticateDomain(packet.getFrom().getDomain(), packet.getTo().getDomain());
if (created) {
// A connection to the remote server was created so get the route and send the packet
ChannelHandler route = routingTable.getRoute(packet.getTo());
if (route != null) { if (route != null) {
route.process(reply); route.process(packet);
} }
} else {
else if (packet instanceof Message) { throw new Exception("Failed to create connection to remote server");
Message reply = new Message();
reply.setID(packet.getID());
reply.setTo(from);
reply.setFrom(to);
reply.setType(((Message)packet).getType());
reply.setThread(((Message)packet).getThread());
reply.setError(PacketError.Condition.remote_server_not_found);
ChannelHandler route = routingTable.getRoute(reply.getTo());
if (route != null) {
route.process(reply);
} }
} }
else {
throw new Exception("Failed to create connection to remote server");
}
} }
catch (UnauthorizedException e) {
} private void returnErrorToSender(Packet packet) {
catch (Exception e) { XMPPServer server = XMPPServer.getInstance();
Log.warn("Error returning error to sender. Original packet: " + packet, e); JID from = packet.getFrom();
JID to = packet.getTo();
if (!server.isLocal(from) && !XMPPServer.getInstance().matchesComponent(from) &&
!server.isLocal(to) && !XMPPServer.getInstance().matchesComponent(to)) {
// Do nothing since the sender and receiver of the packet that failed to reach a remote
// server are not local users. This prevents endless loops if the FROM or TO address
// are non-existen addresses
return;
}
// TODO Send correct error condition: timeout or not_found depending on the real error
try {
if (packet instanceof IQ) {
IQ reply = new IQ();
reply.setID(((IQ) packet).getID());
reply.setTo(from);
reply.setFrom(to);
reply.setChildElement(((IQ) packet).getChildElement().createCopy());
reply.setError(PacketError.Condition.remote_server_not_found);
ChannelHandler route = routingTable.getRoute(reply.getTo());
if (route != null) {
route.process(reply);
}
}
else if (packet instanceof Presence) {
Presence reply = new Presence();
reply.setID(packet.getID());
reply.setTo(from);
reply.setFrom(to);
reply.setError(PacketError.Condition.remote_server_not_found);
ChannelHandler route = routingTable.getRoute(reply.getTo());
if (route != null) {
route.process(reply);
}
}
else if (packet instanceof Message) {
Message reply = new Message();
reply.setID(packet.getID());
reply.setTo(from);
reply.setFrom(to);
reply.setType(((Message)packet).getType());
reply.setThread(((Message)packet).getThread());
reply.setError(PacketError.Condition.remote_server_not_found);
ChannelHandler route = routingTable.getRoute(reply.getTo());
if (route != null) {
route.process(reply);
}
}
}
catch (UnauthorizedException e) {
}
catch (Exception e) {
Log.warn("Error returning error to sender. Original packet: " + packet, e);
}
} }
}
/** public void addPacket(Packet packet) {
* Shuts down the thread that consumes the queued packets and also stops the pool packets.add(packet);
* of threads that actually send the packets to the remote servers. }
*/
public void shutdown() {
threadPool.shutdown();
shutdown = true;
}
public JID getAddress() { public String getDomain() {
// TODO Will somebody send this message to me???? return domain;
return null; }
}
public void process(Packet packet) { public boolean isDone() {
// Queue the packet. Another process will process the queued packets. return packets.isEmpty();
packets.add(packet.createCopy()); }
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment