/* * Copyright 2004 - 2008 Christian Sprajc. All rights reserved. * * This file is part of PowerFolder. * * PowerFolder is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation. * * PowerFolder 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with PowerFolder. If not, see <http://www.gnu.org/licenses/>. * * $Id$ */ package de.dal33t.powerfolder.net; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.TimerTask; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; import de.dal33t.powerfolder.Controller; import de.dal33t.powerfolder.Member; import de.dal33t.powerfolder.PFComponent; import de.dal33t.powerfolder.light.MemberInfo; import de.dal33t.powerfolder.message.RelayedMessage; import de.dal33t.powerfolder.message.RelayedMessageExt; import de.dal33t.powerfolder.message.RelayedMessage.Type; import de.dal33t.powerfolder.util.Reject; import de.dal33t.powerfolder.util.TransferCounter; import de.dal33t.powerfolder.util.Waiter; /** * Listens for incoming relayed messages and * <p> * 1) Processes it if destination = myself. = Let RelayedConHandler of Member * process the message. * <p> * 2) Send the message to the destination if connected. * <p> * TRAC #597. * * @author <a href="mailto:totmacher@powerfolder.com">Christian Sprajc</a> * @version $Revision: 1.5 $ */ public class RelayedConnectionManager extends PFComponent { private static final Logger log = Logger .getLogger(RelayedConnectionManager.class.getName()); private static long nextConnectionId = 0; /** * Connection handler that are in pending state. Pending means there is a * ConnectionHanlder which is not yet connected with it's member (node). */ private Collection<AbstractRelayedConnectionHandler> pendingConHans; private RelayFinder relayFinder; private volatile Member currentRelay; private Lock pendingConHansLock = new ReentrantLock(); private TransferCounter counter; private boolean printStats; private long nRelayedMsgs; public RelayedConnectionManager(Controller controller) { super(controller); pendingConHans = new CopyOnWriteArrayList<AbstractRelayedConnectionHandler>(); relayFinder = controller.getDistribution().createRelayFinder(); counter = new TransferCounter(); printStats = false; } public void start() { getController().scheduleAndRepeat(new RelayConnectTask(), 1000L * 20, 1000L * 20); } /** * Creates and initializes a relayed channel via a relay. The returned * ConnectionHandler is in init state. * * @param destination * the remote destination to connect to * @return the relayed connection handler. * @throws ConnectionException */ public ConnectionHandler initRelayedConnectionHandler(MemberInfo destination) throws ConnectionException { if (getController().getMySelf().getInfo().equals(destination)) { throw new ConnectionException( "Illegal relayed loopback connection detection to myself"); } Member relay = getRelay(); if (relay == null) { throw new ConnectionException( "Unable to open relayed connection to " + destination + ". No relay found!"); } if (isFiner()) { logFiner("Using relay " + relay + " for connection to " + destination + " / " + destination.id); } if (isFiner()) { logFiner("Sending SYN for relayed connection to " + destination.nick); } long connectionId; synchronized (RelayedConnectionManager.class) { connectionId = nextConnectionId++; } AbstractRelayedConnectionHandler relHan = getController() .getIOProvider().getConnectionHandlerFactory() .createRelayedConnectionHandler(destination, connectionId, relay); pendingConHansLock.lock(); pendingConHans.add(relHan); pendingConHansLock.unlock(); if (pendingConHans.size() > 20) { logSevere(pendingConHans.size() + " PENDING RELAYED CONNECTION HANDLERS found: " + pendingConHans); } RelayedMessage synMsg = relay.getProtocolVersion() >= 108 ? new RelayedMessageExt(Type.SYN, getController().getMySelf() .getInfo(), destination, connectionId, null) : new RelayedMessage(Type.SYN, getController().getMySelf() .getInfo(), destination, connectionId, null); try { relay.sendMessage(synMsg); waitForAckOrNack(relHan); relHan.init(); } catch (ConnectionException e) { relHan.shutdown(); removePedingRelayedConnectionHandler(relHan); throw e; } return relHan; } /** * Callback from <code>AbstractRelayedConnectionHandler</code> to inform, * that the handler is not longer pending (=on shutdown or assigend to his * <code>Member</code>). * * @param conHan */ public void removePedingRelayedConnectionHandler( AbstractRelayedConnectionHandler conHan) { Reject.ifNull(conHan, "ConnectionHandler is null"); // logWarning("Removing pend. con han: " + conHan); pendingConHansLock.lock(); pendingConHans.remove(conHan); pendingConHansLock.unlock(); } /** * Callback method from <code>Member</code>. * * @param receivedFrom * the node/relay which relayed the message * @param message * the message */ public void handleRelayedMessage(final Member receivedFrom, final RelayedMessage message) { if (getController().getMySelf().getInfo().equals( message.getDestination())) { // This is a message for US! processMessageForMySelf(receivedFrom, message); } else { // Route message to destination member if possible. relayMessage(receivedFrom, message); } } /** * For TESTS only. * * @param relayFinder */ public void setRelayFiner(RelayFinder relayFinder) { Reject.ifNull(relayFinder, "relayFinder"); this.relayFinder = relayFinder; this.currentRelay = relayFinder.findRelay(getController() .getNodeManager()); } /** * @return the relaying node or null if no relay found */ public Member getRelay() { if (currentRelay == null) { currentRelay = relayFinder.findRelay(getController() .getNodeManager()); } return currentRelay; } public boolean isRelay(MemberInfo nodeInfo) { Reject.ifNull(nodeInfo, "Node info is null"); Member node = nodeInfo.getNode(getController(), false); return node != null && isRelay(node); } public boolean isRelay(Member node) { return getRelay() != null && node.equals(getRelay()); } public TransferCounter getTransferCounter() { return counter; } // Internal *************************************************************** private void relayMessage(final Member receivedFrom, final RelayedMessage message) { Member destinationMember = message.getDestination().getNode( getController(), true); if (!destinationMember.isCompletelyConnected()) { Type type = message.getType().equals(Type.SYN) ? Type.NACK : Type.EOF; RelayedMessage msg = receivedFrom.getProtocolVersion() >= 108 ? new RelayedMessageExt(type, message.getDestination(), message .getSource(), message.getConnectionId(), null) : new RelayedMessage(type, message.getDestination(), message .getSource(), message.getConnectionId(), null); receivedFrom.sendMessagesAsynchron(msg); if (isFiner()) { logFiner("Unable to relay message. " + destinationMember.getNick() + " not connected, sending EOF/NACK. msg: " + message); } return; } if (isFiner()) { logFiner("Relaying msg to " + destinationMember.getNick() + ". msg: " + message); } if (!printStats) { printStats = true; logInfo("Acting as relay. Received from " + receivedFrom.getNick() + ", msg: " + message); getController().scheduleAndRepeat(new TimerTask() { @Override public void run() { logFine("Relay stats (RelayedCon): " + nRelayedMsgs + " msgs relayed. " + counter); } }, 10000); } RelayedMessage msg4Destination = message; // Poor destination. Does not support Ext version of RelayedMessage if (destinationMember.getProtocolVersion() < 108 && message instanceof RelayedMessageExt) { msg4Destination = new RelayedMessage(message.getType(), message .getSource(), message.getDestination(), message .getConnectionId(), message.getPayload()); } if (message.getType().equals(RelayedMessage.Type.DATA_ZIPPED)) { try { destinationMember.sendMessage(msg4Destination); } catch (ConnectionException e) { log.log(Level.WARNING, "Connection broken while relaying message to " + destinationMember.getNick() + ". " + e); log.log(Level.FINER, e.toString(), e); RelayedMessage eofMsg = receivedFrom.getProtocolVersion() >= 108 ? new RelayedMessageExt(Type.EOF, message.getDestination(), message.getSource(), message.getConnectionId(), null) : new RelayedMessage(Type.EOF, message.getDestination(), message.getSource(), message.getConnectionId(), null); receivedFrom.sendMessagesAsynchron(eofMsg); } } else { destinationMember.sendMessagesAsynchron(msg4Destination); } } private void processMessageForMySelf(final Member receivedFrom, final RelayedMessage message) { // Deliver to RelayedConnectionHanlder of Remote member final AbstractRelayedConnectionHandler peer = resolveRelHan(message); switch (message.getType()) { case SYN : if (isFiner()) { logFiner("SYN received from " + message.getSource().nick); } if (!getController().getIOProvider() .getConnectionHandlerFactory().useRelayedConnections()) { RelayedMessage nackMsg = receivedFrom.getProtocolVersion() >= 108 ? new RelayedMessageExt(Type.NACK, getController() .getMySelf().getInfo(), message.getSource(), message.getConnectionId(), null) : new RelayedMessage(Type.NACK, getController() .getMySelf().getInfo(), message.getSource(), message.getConnectionId(), null); receivedFrom.sendMessagesAsynchron(nackMsg); return; } final AbstractRelayedConnectionHandler relHan = getController() .getIOProvider().getConnectionHandlerFactory() .createRelayedConnectionHandler(message.getSource(), message.getConnectionId(), receivedFrom); pendingConHansLock.lock(); pendingConHans.add(relHan); pendingConHansLock.unlock(); Runnable initializer = new ConnectionInitializer(message, relHan, receivedFrom); getController().getIOProvider().startIO(initializer); return; case ACK : if (isFiner()) { logFiner("ACK received from " + message.getSource().nick); } if (peer != null) { peer.setAckReceived(true); } return; case NACK : if (isFiner()) { logFiner("NACK received from " + message.getSource().nick); } if (peer != null) { peer.setNackReceived(true); peer.shutdownWithMember(); removePedingRelayedConnectionHandler(peer); } return; case EOF : if (isFiner()) { logFiner("EOF received from " + message.getSource().nick); } if (peer != null) { peer.shutdownWithMember(); removePedingRelayedConnectionHandler(peer); } return; } Reject.ifFalse(message.getType().equals(Type.DATA_ZIPPED), "Only zipped data allowed"); if (isFiner()) { logFiner("DATA received from " + message.getSource().nick + ": " + message); } // if (!sourceMember.isCompletelyConnected()) { // log() // .warn("Relayed connection was shutdown to " + sourceMember); // RelayedMessage eofMsg = new RelayedMessage(Type.EOF, // getController().getMySelf().getInfo(), message.getSource(), // null); // receivedFrom.sendMessagesAsynchron(eofMsg); // } if (peer == null) { if (isFiner()) { logFiner("Got unknown peer, while processing relayed message from " + message.getSource().nick); } RelayedMessage eofMsg = receivedFrom.getProtocolVersion() >= 108 ? new RelayedMessageExt(Type.EOF, message.getDestination(), message.getSource(), message.getConnectionId(), null) : new RelayedMessage(Type.EOF, getController().getMySelf() .getInfo(), message.getSource(), message.getConnectionId(), null); receivedFrom.sendMessagesAsynchron(eofMsg); return; } // Actual relay of message peer.receiveRelayedMessage(message); } private AbstractRelayedConnectionHandler resolveRelHan( RelayedMessage message) { Member sourceMember = message.getSource() .getNode(getController(), true); ConnectionHandler peer = sourceMember.getPeer(); if (peer == null) { // Search in pending con handlers try { pendingConHansLock.lock(); for (AbstractRelayedConnectionHandler relHel : pendingConHans) { if (relHel.getRemote().equals(message.getSource()) && (relHel.getConnectionId() == message .getConnectionId())) { // Found in pending! peer = relHel; break; } } } finally { pendingConHansLock.unlock(); } } if (peer instanceof AbstractRelayedConnectionHandler) { return (AbstractRelayedConnectionHandler) peer; } if (message.getType().equals(Type.DATA_ZIPPED) && sourceMember.isInteresting()) { if (isFine()) { logFine("Unable to resolved pending con handler for " + message.getSource().nick + ", conId: " + message.getConnectionId() + ". Got these: " + pendingConHans + ". msg: " + message); } } return null; } private void waitForAckOrNack(AbstractRelayedConnectionHandler relHan) throws ConnectionException { Waiter waiter = new Waiter(60L * 1000L); if (isFiner()) { logFiner("Waiting for ack on " + relHan); } while (!waiter.isTimeout()) { if (relHan.isAckReceived()) { if (isFiner()) { logFiner("Got ack on " + relHan); } return; } if (relHan.isNackReceived()) { throw new ConnectionException( "NACK received: Unable to open relayed connection to " + relHan.getRemote().nick); } if (!relHan.getRelay().isCompletelyConnected()) { throw new ConnectionException( "Unable to open relayed connection to " + relHan.getRemote().nick + " relay " + relHan.getRelay() + " disconnected."); } try { waiter.waitABit(); } catch (RuntimeException e) { throw new ConnectionException("Shutdown", e); } } if (!relHan.isAckReceived()) { throw new ConnectionException( "Did not receive a ack after 60s from " + relHan); } } // Internal classes ******************************************************* private final class ConnectionInitializer implements Runnable { private final RelayedMessage message; private final AbstractRelayedConnectionHandler relHan; private final Member receivedFrom; private ConnectionInitializer(RelayedMessage message, AbstractRelayedConnectionHandler relHan, Member receivedFrom) { this.message = message; this.relHan = relHan; this.receivedFrom = receivedFrom; } public void run() { try { if (isFiner()) { logFiner("Sending ACK to " + message.getSource().nick); } RelayedMessage ackMsg = receivedFrom.getProtocolVersion() >= 108 ? new RelayedMessageExt(Type.ACK, getController() .getMySelf().getInfo(), message.getSource(), relHan .getConnectionId(), null) : new RelayedMessage(Type.ACK, getController().getMySelf() .getInfo(), message.getSource(), relHan .getConnectionId(), null); receivedFrom.sendMessagesAsynchron(ackMsg); relHan.init(); getController().getNodeManager().acceptConnection(relHan); } catch (ConnectionException e) { relHan.shutdown(); logFine("Unable to accept connection: " + relHan + ". " + e.toString()); logFiner("ConnectionException", e); RelayedMessage nackMsg = receivedFrom.getProtocolVersion() >= 108 ? new RelayedMessageExt(Type.NACK, getController() .getMySelf().getInfo(), message.getSource(), message .getConnectionId(), null) : new RelayedMessage(Type.NACK, getController().getMySelf() .getInfo(), message.getSource(), message .getConnectionId(), null); receivedFrom.sendMessagesAsynchron(nackMsg); } finally { removePedingRelayedConnectionHandler(relHan); } } } private class RelayConnectTask extends TimerTask { @Override public void run() { if (!getController().isStarted()) { return; } if (isRelay(getController().getMySelf().getInfo())) { return; } if (getController().isLanOnly()) { return; } if (!getController().getNodeManager().isStarted()) { return; } currentRelay = relayFinder.findRelay(getController() .getNodeManager()); if (isFine()) { if (currentRelay != null) { logFine("Using relay: " + currentRelay); } else { logFine("No relay server found yet"); } } if (currentRelay == null || currentRelay.isConnected() || currentRelay.isConnecting()) { return; } if (isFine()) { logFine("Triing to connect to relay: " + currentRelay + " id: " + currentRelay.getId()); } currentRelay.markForImmediateConnect(); } } public static class ServerIsRelayFinder implements RelayFinder { public Member findRelay(NodeManager nodeManager) { Member defaultServer = nodeManager.getController().getOSClient() .getServer(); List<Member> candidates = new LinkedList<Member>(); for (Member node : nodeManager.getNodesAsCollection()) { if (node.isServer() || node.equals(defaultServer)) { if (node.isCompletelyConnected()) { candidates.add(node); } } } if (candidates.size() > 1) { candidates.remove(defaultServer); Collections.shuffle(candidates); } return candidates.isEmpty() ? null : candidates.get(0); } } }