/* * Tigase Jabber/XMPP Server * Copyright (C) 2004-2012 "Artur Hefczyc" <artur.hefczyc@tigase.org> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. Look for COPYING file in the top folder. * If not, see http://www.gnu.org/licenses/. * * $Rev$ * Last modified by $Author$ * $Date$ */ package tigase.server.xmppserver; //~--- non-JDK imports -------------------------------------------------------- import tigase.net.ConnectionType; import tigase.net.SocketType; import tigase.server.Packet; import tigase.util.DNSEntry; import tigase.util.DNSResolver; import tigase.xmpp.Authorization; import tigase.xmpp.PacketErrorTypeException; //~--- JDK imports ------------------------------------------------------------ import java.net.UnknownHostException; import java.util.Map; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.TreeMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; //~--- classes ---------------------------------------------------------------- /** * Created: Jun 14, 2010 12:32:49 PM * * @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a> * @version $Rev$ */ public class CIDConnections { private static final Logger log = Logger.getLogger(CIDConnections.class.getName()); private static final Timer outgoingOpenTasks = new Timer("S2S outgoing open tasks", true); // ~--- fields --------------------------------------------------------------- private CID cid = null; private S2SConnectionSelector connectionSelector = null; private long firstWaitingTime = 0; private S2SConnectionHandlerIfc<S2SIOService> handler = null; private int max_in_conns = 4; private int max_out_conns = 4; private int max_out_conns_per_ip = 2; private long max_waiting_time = 15 * 60 * 1000; private AtomicBoolean outgoingOpenInProgress = new AtomicBoolean(false); private ReentrantLock sendInProgress = new ReentrantLock(); private Set<S2SConnection> outgoing_handshaking = new ConcurrentSkipListSet<S2SConnection>(); private Set<S2SConnection> outgoing = new ConcurrentSkipListSet<S2SConnection>(); private Set<S2SConnection> incoming = new ConcurrentSkipListSet<S2SConnection>(); /** * (SessionID, dbKey) pairs */ private Map<String, String> dbKeys = new ConcurrentSkipListMap<String, String>(); private ConcurrentLinkedQueue<Packet> waitingPackets = new ConcurrentLinkedQueue<Packet>(); // ~--- constructors --------------------------------------------------------- public void resetOutgoingInProgress() { outgoingOpenInProgress.set(false); } public boolean getOutgoingInProgress() { return outgoingOpenInProgress.get(); } /** * Constructs ... * * * @param cid * @param handler * @param selector * @param maxInConns * @param maxOutConns * @param maxOutConnsPerIP * @param max_waiting_time */ public CIDConnections(CID cid, S2SConnectionHandlerIfc<S2SIOService> handler, S2SConnectionSelector selector, int maxInConns, int maxOutConns, int maxOutConnsPerIP, long max_waiting_time) { this.cid = cid; this.handler = handler; this.connectionSelector = selector; this.max_in_conns = maxInConns; this.max_out_conns = maxOutConns; this.max_out_conns_per_ip = maxOutConnsPerIP; this.max_waiting_time = max_waiting_time; } // ~--- methods -------------------------------------------------------------- /** * Method description * * * @param sessId * @param key */ public void addDBKey(String sessId, String key) { dbKeys.put(sessId, key); } /** * Method description * * * @param serv */ public void addIncoming(S2SIOService serv) { S2SConnection s2s_conn = serv.getS2SConnection(); if (s2s_conn == null) { s2s_conn = new S2SConnection(handler, serv.getRemoteAddress()); s2s_conn.setS2SIOService(serv); serv.setS2SConnection(s2s_conn); } // TODO: check if this should be moved inside the IF incoming.add(s2s_conn); } /** * Method description * * * @param serv */ public void connectionAuthenticated(S2SIOService serv) { if (log.isLoggable(Level.FINER)) { log.log(Level.FINER, "{0}, connection is authenticated.", serv); } serv.addCID(cid); if (serv.connectionType() == ConnectionType.connect) { // Release the 'lock' outgoingOpenInProgress.set(false); S2SConnection s2s_conn = serv.getS2SConnection(); outgoing_handshaking.remove(s2s_conn); outgoing.add(s2s_conn); sendPacket(null); } } /** * Method description * * * * @param sessionId */ public void connectionAuthenticated(String sessionId) { S2SConnection s2s_conn = getS2SConnectionForSessionId(sessionId); if (s2s_conn != null) { connectionAuthenticated(s2s_conn.getS2SIOService()); } } /** * Method description * * * @param serv */ public void connectionStopped(S2SIOService serv) { S2SConnection s2s_conn = serv.getS2SConnection(); if (s2s_conn == null) { log.log(Level.INFO, "s2s_conn not set for serv: {0}", serv); return; } if (serv.getSessionId() != null) { dbKeys.remove(serv.getSessionId()); } switch (serv.connectionType()) { case connect: // Release the 'lock' outgoingOpenInProgress.set(false); outgoing.remove(s2s_conn); outgoing_handshaking.remove(s2s_conn); if (!waitingPackets.isEmpty()) { checkOpenConnections(); } break; case accept: incoming.remove(s2s_conn); break; default: } } // ~--- get methods ---------------------------------------------------------- /** * Method description * * * @param key_sessionId * * @return */ public String getDBKey(String key_sessionId) { return dbKeys.get(key_sessionId); } /** * Method description * * * @return */ public int getDBKeysCount() { return dbKeys.size(); } /** * Method description * * * @return */ public int getIncomingCount() { int result = 0; for (S2SConnection s2SConnection : incoming) { if (s2SConnection.isConnected()) { ++result; } } return result; } /** * Method description * * * @return */ public int getIncomingTLSCount() { int result = 0; for (S2SConnection s2SConnection : incoming) { S2SIOService serv = s2SConnection.getS2SIOService(); if (serv.isConnected() && (serv.getSessionData().get(S2SIOService.CERT_CHECK_RESULT) != null)) { ++result; } } return result; } /** * Method description * * * @return */ public int getMaxOutConns() { return this.max_out_conns; } /** * Method description * * * @return */ public int getMaxOutConnsPerIP() { return this.max_out_conns_per_ip; } /** * Method description * * * @return */ public int getOutgoingCount() { int result = 0; for (S2SConnection s2SConnection : outgoing) { if (s2SConnection.isConnected()) { ++result; } } return result; } /** * Method description * * * @return */ public int getOutgoingHandshakingCount() { int result = 0; for (S2SConnection s2SConnection : outgoing_handshaking) { if (s2SConnection.isConnected()) { ++result; } } return result; } /** * Method description * * * @return */ public int getOutgoingTLSCount() { int result = 0; for (S2SConnection s2SConnection : outgoing) { S2SIOService serv = s2SConnection.getS2SIOService(); if (serv.isConnected() && (serv.getSessionData().get(S2SIOService.CERT_CHECK_RESULT) != null)) { ++result; } } return result; } /** * Method description * * * @param sessionId * * @return */ public S2SConnection getS2SConnectionForSessionId(String sessionId) { S2SConnection s2s_conn = null; for (S2SConnection s2sc : incoming) { if ((s2sc.getS2SIOService() != null) && sessionId.equals(s2sc.getS2SIOService().getSessionId())) { s2s_conn = s2sc; break; } } if (s2s_conn == null) { for (S2SConnection s2sc : outgoing) { if ((s2sc.getS2SIOService() != null) && sessionId.equals(s2sc.getS2SIOService().getSessionId())) { s2s_conn = s2sc; break; } } } if (s2s_conn == null) { for (S2SConnection s2sc : outgoing_handshaking) { if ((s2sc.getS2SIOService() != null) && sessionId.equals(s2sc.getS2SIOService().getSessionId())) { s2s_conn = s2sc; break; } } } return s2s_conn; } /** * Method description * * * @return */ public int getWaitingControlCount() { int result = 0; for (S2SConnection s2sc : incoming) { result += s2sc.getWaitingControlCount(); } for (S2SConnection s2sc : outgoing) { result += s2sc.getWaitingControlCount(); } for (S2SConnection s2sc : outgoing_handshaking) { result += s2sc.getWaitingControlCount(); } return result; } /** * Method description * * * @return */ public int getWaitingCount() { return waitingPackets.size(); } // ~--- methods -------------------------------------------------------------- /** * Method description * * * @param port_props */ public void reconnectionFailed(Map<String, Object> port_props) { S2SConnection s2s_conn = (S2SConnection) port_props.get(S2SIOService.S2S_CONNECTION_KEY); if (s2s_conn == null) { log.log(Level.INFO, "s2s_conn not set for serv: {0}", port_props); return; } ConnectionType type = (ConnectionType) port_props.get("type"); if (type != null) { switch (type) { case connect: // Release the 'lock' outgoingOpenInProgress.set(false); outgoing.remove(s2s_conn); outgoing_handshaking.remove(s2s_conn); if (!this.waitingPackets.isEmpty()) { checkOpenConnections(); } break; case accept: this.incoming.remove(s2s_conn); break; default: } } else { log.log(Level.INFO, "ConnectionType not set for serv: {0}", port_props); } } /** * Method description * * * * @param sessionId * @param packet * @return */ public boolean sendControlPacket(String sessionId, Packet packet) { // Seraching for a correct connection // TODO: speed it up somehow, maybe verify can be only sent to incoming // and result to outgoing? Check it out S2SConnection s2s_conn = getS2SConnectionForSessionId(sessionId); if (s2s_conn != null) { s2s_conn.addControlPacket(packet); s2s_conn.sendAllControlPackets(); return true; } else { if (log.isLoggable(Level.FINE)) { log.log(Level.FINE, "Control packet: {0} could not be sent as there is no connection " + "for the session id: {1}", new Object[] { packet, sessionId }); } return false; } } /** * Method description * * * @param verify_req */ public void sendHandshakingOnly(final Packet verify_req) { outgoingOpenTasks.schedule(new TimerTask() { @Override public void run() { try { DNSEntry dns_entry = DNSResolver.getHostSRV_Entry(cid.getRemoteHost()); S2SConnection s2s_conn = new S2SConnection(handler, dns_entry.getIp()); s2s_conn.addControlPacket(verify_req); Map<String, Object> port_props = new TreeMap<String, Object>(); port_props.put(S2SIOService.HANDSHAKING_ONLY_KEY, S2SIOService.HANDSHAKING_ONLY_KEY); // it looks like we are sending verify requests only on handshaking-only connection // so there is only one domain for verification port_props.put(S2SIOService.HANDSHAKING_DOMAIN_KEY, verify_req.getStanzaTo().toString()); initNewConnection(dns_entry.getIp(), dns_entry.getPort(), s2s_conn, port_props); } catch (UnknownHostException ex) { log.log(Level.INFO, "Remote host not found: " + cid.getRemoteHost(), ex); } } }, 0); } /** * Method description * * * @param packet */ public void sendPacket(Packet packet) { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Sending packets."); } if (packet != null) { if ((firstWaitingTime == 0) || waitingPackets.isEmpty()) { firstWaitingTime = System.currentTimeMillis(); } waitingPackets.offer(packet); } if (sendInProgress.tryLock()) { try { boolean packetSent = false; Packet waiting = null; while ((waiting = waitingPackets.peek()) != null) { S2SConnection s2s_conn = getOutgoingConnection(waiting); if (s2s_conn != null) { try { packetSent = s2s_conn.sendPacket(waiting); waitingPackets.poll(); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Packet: {0} sent over connection: {1}", new Object[] { waiting, s2s_conn.getS2SIOService() }); } } catch (Exception ex) { log.log(Level.FINE, "A problem sending packet, connection broken? Retrying later. {0}", waiting); } } else { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "There is no connection available to send the packet: {0}", waiting); } break; } } if (!packetSent) { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "No packet could be sent, trying to open more connections: {0}", cid); } checkOpenConnections(); } else { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Some packets were sent, not trying to open more connections: {0}", cid); } } } finally { sendInProgress.unlock(); } } } private void checkOpenConnections() { if (outgoingOpenInProgress.compareAndSet(false, true)) { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Scheduling task for openning a new connection for: {0}", cid); } outgoingOpenTasks.schedule(new TimerTask() { @Override public void run() { boolean result = false; try { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Running scheduled task for openning a new connection for: {0}", cid); } result = openOutgoingConnections(); } catch (Exception e) { log.log(Level.WARNING, "uncaughtException in the connection opening thread: ", e); } finally { } if (!result) { outgoingOpenInProgress.set(false); } } }, 0); } else { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Outgoing open in progress, skipping for: {0}", cid); } } } // ~--- get methods ---------------------------------------------------------- private int getOpenForIP(String ip) { int result = 0; for (S2SConnection s2SConnection : outgoing) { if (ip.equals(s2SConnection.getIPAddress())) { ++result; } } for (S2SConnection s2SConnection : outgoing_handshaking) { if (ip.equals(s2SConnection.getIPAddress())) { ++result; } } return result; } private S2SConnection getOutgoingConnection(Packet packet) { return connectionSelector.selectConnection(packet, outgoing); } // ~--- methods -------------------------------------------------------------- private void initNewConnection(String ip, int port, S2SConnection s2s_conn, Map<String, Object> port_props) { outgoing_handshaking.add(s2s_conn); port_props.put(S2SIOService.S2S_CONNECTION_KEY, s2s_conn); port_props.put("remote-ip", ip); port_props.put("local-hostname", cid.getLocalHost()); port_props.put("remote-hostname", cid.getRemoteHost()); port_props.put("ifc", new String[] { ip }); port_props.put("socket", SocketType.plain); port_props.put("type", ConnectionType.connect); port_props.put("port-no", port); port_props.put("cid", cid); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "STARTING new connection: {0}", cid); log.log(Level.FINEST, "{0} connection params: {1}", new Object[] { cid, port_props }); } handler.initNewConnection(port_props); } private boolean openOutgoingConnections() { boolean result = false; try { // Check whether all active connections are still active for (S2SConnection out_conn : outgoing) { if (!out_conn.isConnected()) { outgoing.remove(out_conn); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Removing inactive connection: {0}", out_conn); } } } if (firstWaitingTime + max_waiting_time <= System.currentTimeMillis()) { sendPacketsBack(); firstWaitingTime = 0; if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "S2S Timeout expired, sending back: {0}", waitingPackets); } return result; } int all_outgoing = outgoing.size() + outgoing_handshaking.size(); if (all_outgoing >= max_out_conns) { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Exceeded max number of outgoing connections, not doing anything: {0}", all_outgoing); } return result; } if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Checking DNS for host: {0} for: {1}", new Object[] { cid.getRemoteHost(), cid }); } // Check DNS entries DNSEntry[] dns_entries = DNSResolver.getHostSRV_Entries(cid.getRemoteHost()); // Activate 'missing' connections for (DNSEntry dNSEntry : dns_entries) { int openForIP = getOpenForIP(dNSEntry.getIp()); for (int i = openForIP; i < max_out_conns_per_ip; i++) { if (dNSEntry.getIp().equals("127.0.0.1")) { // DNS misconfiguration for the remote server (icq.jabber.cz for // example) // Now we assume: UnknownHostException if (log.isLoggable(Level.INFO)) { log.log(Level.INFO, "DNS misconfiguration for domain: {0}, for: {1}", new Object[] { cid.getRemoteHost(), cid }); } throw new UnknownHostException("DNS misconfiguration for domain: " + cid.getRemoteHost()); } // Create a new connection S2SConnection s2s_conn = new S2SConnection(handler, dNSEntry.getIp()); Map<String, Object> port_props = new TreeMap<String, Object>(); initNewConnection(dNSEntry.getIp(), dNSEntry.getPort(), s2s_conn, port_props); result = true; if (++all_outgoing >= max_out_conns) { return result; } } } } catch (UnknownHostException ex) { log.log(Level.INFO, "Remote host not found: " + cid.getRemoteHost() + ", for: " + cid, ex); sendPacketsBack(); } return result; } private void sendPacketsBack() { Packet p = null; while ((p = waitingPackets.poll()) != null) { try { handler.addOutPacket(Authorization.REMOTE_SERVER_NOT_FOUND.getResponseMessage(p, "S2S - destination host not found", true)); } catch (PacketErrorTypeException e) { log.log(Level.WARNING, "Packet: {0} processing exception: {1}", new Object[] { p.toString(), e }); } } } } // ~ Formatted in Sun Code Convention // ~ Formatted by Jindent --- http://www.jindent.com