package com.limegroup.gnutella; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.limewire.core.api.connection.ConnectionLifecycleEventType; import org.limewire.core.settings.ApplicationSettings; import org.limewire.core.settings.ConnectionSettings; import org.limewire.core.settings.UltrapeerSettings; import org.limewire.inject.EagerSingleton; import org.limewire.inspection.Inspectable; import org.limewire.inspection.InspectableForSize; import org.limewire.inspection.InspectablePrimitive; import org.limewire.inspection.InspectionPoint; import org.limewire.io.Connectable; import org.limewire.io.GUID; import org.limewire.io.IpPort; import org.limewire.io.NetworkInstanceUtils; import org.limewire.io.NetworkUtils; import org.limewire.lifecycle.Service; import org.limewire.logging.Log; import org.limewire.logging.LogFactory; import org.limewire.net.ConnectionDispatcher; import org.limewire.net.SocketsManager; import org.limewire.net.SocketsManager.ConnectType; import org.limewire.net.address.StrictIpPortSet; import org.limewire.util.SystemUtils; import org.limewire.util.Version; import org.limewire.util.VersionFormatException; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.name.Named; import com.limegroup.gnutella.connection.Connection; import com.limegroup.gnutella.connection.ConnectionCheckerManager; import com.limegroup.gnutella.connection.ConnectionLifecycleEvent; import com.limegroup.gnutella.connection.ConnectionLifecycleListener; import com.limegroup.gnutella.connection.GnetConnectObserver; import com.limegroup.gnutella.connection.GnutellaConnectionEvent; import com.limegroup.gnutella.connection.RoutedConnection; import com.limegroup.gnutella.connection.RoutedConnectionFactory; import com.limegroup.gnutella.filters.IPFilter; import com.limegroup.gnutella.handshaking.HandshakeResponse; import com.limegroup.gnutella.handshaking.HandshakeStatus; import com.limegroup.gnutella.handshaking.HeaderNames; import com.limegroup.gnutella.messages.Message; import com.limegroup.gnutella.messages.PingRequest; import com.limegroup.gnutella.messages.PingRequestFactory; import com.limegroup.gnutella.messages.Message.Network; import com.limegroup.gnutella.messages.vendor.CapabilitiesVMFactory; import com.limegroup.gnutella.messages.vendor.QueryStatusResponse; import com.limegroup.gnutella.messages.vendor.TCPConnectBackVendorMessage; import com.limegroup.gnutella.messages.vendor.UDPConnectBackVendorMessage; import com.limegroup.gnutella.simpp.SimppListener; import com.limegroup.gnutella.simpp.SimppManager; /** * The list of all RoutedConnection's. Provides a factory method for creating * user-requested outgoing connections, accepts incoming connections, and * fetches "automatic" outgoing connections as needed. Creates threads for * handling these connections when appropriate. * * Because this is the only list of all connections, it plays an important role * in message broadcasting. For this reason, the code is highly tuned to avoid * locking in the getInitializedConnections() methods. Adding and removing * connections is a slower operation.<p> * * LimeWire follows the following connection strategy:<br> * As a leaf, LimeWire will ONLY connect to 'good' Ultrapeers. The definition * of good is constantly changing. For a current view of 'good', review * HandshakeResponse.isGoodUltrapeer(). LimeWire leaves will NOT deny * a connection to an ultrapeer even if they've reached their maximum * desired number of connections (currently 3). This means that if 4 * connections resolve simultaneously, the leaf will remain connected to all 4. * <br> * As an Ultrapeer, LimeWire will seek outgoing connections for 5 less than * the number of it's desired peer slots. This is done so that newcomers * on the network have a better chance of finding an ultrapeer with a slot * open. LimeWire ultrapeers will allow ANY other ultrapeer to connect to it, * and to ensure that the network does not become too LimeWire-centric, it * reserves 3 slots for non-LimeWire peers. LimeWire ultrapeers will allow * ANY leaf to connect, so long as there are atleast 15 slots open. Beyond * that number, LimeWire will only allow 'good' leaves. To see what consitutes * a good leaf, view HandshakeResponse.isGoodLeaf(). To ensure that the * network does not remain too LimeWire-centric, it reserves 2 slots for * non-LimeWire leaves.<p> * * ConnectionManager has methods to get up and downstream bandwidth, but it * doesn't quite fit the BandwidthTracker interface. */ @EagerSingleton public class ConnectionManagerImpl implements ConnectionManager, Service { private static final Log LOG = LogFactory.getLog(ConnectionManagerImpl.class); /** The minimum amount of idle time before we switch to using 1 connection. */ private static final int MINIMUM_IDLE_TIME = 30 * 60 * 1000; // 30 minutes /** * The maximum number of times ManagedConnection instances should send UDP * ConnectBack requests. * visible for testing purposes */ static final int MAX_UDP_CONNECT_BACK_ATTEMPTS = 15; /** * The maximum number of times ManagedConnection instances should send TCP * ConnectBack requests. * Visible for testing purposes */ static final int MAX_TCP_CONNECT_BACK_ATTEMPTS = 10; // Older leaves will report the same connections for both inspection points @SuppressWarnings("unused") @InspectionPoint("leaf connections") private final Inspectable LEAF = new LegacyConnectionStats(true); @SuppressWarnings("unused") @InspectionPoint("ultrapeer connections") private final Inspectable UP = new LegacyConnectionStats(false); /** Timestamp for the last time the user selected to disconnect. */ @InspectablePrimitive("last disconnect time") private volatile long _disconnectTime = -1; /** Timestamp for the last time we started trying to connect */ @InspectablePrimitive("last connect time") private volatile long _connectTime = Long.MAX_VALUE; /** Timestamp for the last time we reached our preferred connections */ @InspectablePrimitive("last time preferred reached") @SuppressWarnings("unused") private volatile long _lastFullConnectTime; /** * Timestamp for the time we began automatically connecting. We stop * trying to automatically connect if the user has disconnected since that * time. */ @InspectablePrimitive("begin of automatic connect") private volatile long _automaticConnectTime = 0; /** Flag for whether or not the auto-connection process is in effect. */ @InspectablePrimitive("automatically connecting") private volatile boolean _automaticallyConnecting; /** Timestamp of our last successful connection. */ @InspectablePrimitive("last successful connection") private volatile long _lastSuccessfulConnect = 0; /** Timestamp of the last time we checked to verify that the user has a live Internet connection. */ @InspectablePrimitive("last internet check") private volatile long _lastConnectionCheck = 0; /** Counter for the number of connection attempts we've made. */ @InspectablePrimitive("connection attempts") private volatile int _connectionAttempts; /** The current number of connections we want to maintain. */ @InspectablePrimitive("preferred connections") private volatile int _preferredConnections = -1; /** The number of tcp connect backs sent this session */ @InspectablePrimitive("number tcp connectbacks") private volatile int numTCPConnectBacksLeft; /** The number of udp connect backs sent this session */ @InspectablePrimitive("number udp connectbacks") private volatile int numUDPConnectBacksLeft; /** Threads trying to maintain the NUM_CONNECTIONS. * LOCKING: obtain this. */ private final List<ConnectionFetcher> _fetchers = new ArrayList<ConnectionFetcher>(); /** * Mapping from class C networks to lists of connections that * we have decided not to connect to. * LOCKING: this */ @InspectableForSize("number of skipped class C networks") private final Map<Integer, List<Endpoint>> classCNetworks = new HashMap<Integer,List<Endpoint>>(); /** Connections that have been fetched but not initialized. I don't * know the relation between _initializingFetchedConnections and * _connections (see below). LOCKING: obtain this. */ private final List<RoutedConnection> _initializingFetchedConnections = new ArrayList<RoutedConnection>(); /** * dedicated ConnectionFetcher used by leafs to fetch a * locale matching connection * NOTE: currently this is only used by leafs which will try * to connect to one connection which matches the locale of the * client. */ private ConnectionFetcher _dedicatedPrefFetcher; /** boolean to check if a locale matching connection is needed. */ private volatile boolean _needPref = true; /** * boolean of whether or not the interruption of the prefFetcher thread * has been scheduled. */ private boolean _needPrefInterrupterScheduled = false; /** * List of all connections. The core data structures are lists, which allow * fast iteration for message broadcast purposes. Actually we keep a couple * of lists: the list of all initialized and uninitialized connections * (_connections), the list of all initialized non-leaf connections * (_initializedConnections), and the list of all initialized leaf connections * (_initializedClientConnections). * * INVARIANT: neither _connections, _initializedConnections, nor * _initializedClientConnections contains any duplicates. * INVARIANT: for all c in _initializedConnections, * c.isSupernodeClientConnection()==false * INVARIANT: for all c in _initializedClientConnections, * c.isSupernodeClientConnection()==true * COROLLARY: the intersection of _initializedClientConnections * and _initializedConnections is the empty set * INVARIANT: _initializedConnections is a subset of _connections * INVARIANT: _initializedClientConnections is a subset of _connections * INVARIANT: _shieldedConnections is the number of connections * in _initializedConnections for which isClientSupernodeConnection() * is true. * INVARIANT: _nonLimeWireLeaves is the number of connections * in _initializedClientConnections for which isLimeWire is false * INVARIANT: _nonLimeWirePeers is the number of connections * in _initializedConnections for which isLimeWire is false * * LOCKING: _connections, _initializedConnections and * _initializedClientConnections MUST NOT BE MUTATED. Instead they should * be replaced as necessary with new copies. Before replacing the * structures, obtain this' monitor. This avoids lock overhead when * message broadcasting, though it makes adding/removing connections * much slower. */ //TODO:: why not use sets here?? private volatile List<RoutedConnection> _connections = Collections.emptyList(); private volatile List<RoutedConnection> _initializedConnections = Collections.emptyList(); private volatile List<RoutedConnection> _initializedClientConnections = Collections.emptyList(); private volatile int _shieldedConnections = 0; private volatile int _nonLimeWireLeaves = 0; private volatile int _nonLimeWirePeers = 0; /** number of peers that matches the local locale pref. */ private volatile int _localeMatchingPeers = 0; /** * Variable for the number of times since we attempted to force ourselves * to become an Ultrapeer that we were told to become leaves. If this * number is too great, we give up and become a leaf. */ @InspectablePrimitive("leaf tries") private volatile int _leafTries; /** * The number of demotions to ignore before allowing ourselves to become * a leaf -- this number depends on how good this potential Ultrapeer seems * to be. */ @InspectablePrimitive("demotion limit") private volatile int _demotionLimit = 0; /** The current measured upstream bandwidth in kbytes/sec. */ private volatile float _measuredUpstreamBandwidth = 0.f; /** The current measured downstream bandwidth in kbytes/sec. */ private volatile float _measuredDownstreamBandwidth = 0.f; /** List of event listeners for ConnectionLifeCycleEvents. */ private final CopyOnWriteArrayList<ConnectionLifecycleListener> connectionLifeCycleListeners = new CopyOnWriteArrayList<ConnectionLifecycleListener>(); /** The last version of LimeWire we'll connect to */ private final Version lastGoodVersion; private final NetworkManager networkManager; private final Provider<HostCatcher> hostCatcher; private final Provider<ConnectionDispatcher> connectionDispatcher; private final ScheduledExecutorService backgroundExecutor; private final Provider<SimppManager> simppManager; private final CapabilitiesVMFactory capabilitiesVMFactory; private final RoutedConnectionFactory managedConnectionFactory; private final Provider<QueryUnicaster> queryUnicaster; private final SocketsManager socketsManager; private final ConnectionServices connectionServices; private final Provider<NodeAssigner> nodeAssigner; private final Provider<IPFilter> ipFilter; private final ConnectionCheckerManager connectionCheckerManager; private final PingRequestFactory pingRequestFactory; private final NetworkInstanceUtils networkInstanceUtils; @Inject public ConnectionManagerImpl(NetworkManager networkManager, Provider<HostCatcher> hostCatcher, @Named("global") Provider<ConnectionDispatcher> connectionDispatcher, @Named("backgroundExecutor") ScheduledExecutorService backgroundExecutor, Provider<SimppManager> simppManager, CapabilitiesVMFactory capabilitiesVMFactory, RoutedConnectionFactory managedConnectionFactory, Provider<QueryUnicaster> queryUnicaster, SocketsManager socketsManager, ConnectionServices connectionServices, Provider<NodeAssigner> nodeAssigner, Provider<IPFilter> ipFilter, ConnectionCheckerManager connectionCheckerManager, PingRequestFactory pingRequestFactory, NetworkInstanceUtils networkInstanceUtils) { this.networkManager = networkManager; this.hostCatcher = hostCatcher; this.connectionDispatcher = connectionDispatcher; this.backgroundExecutor = backgroundExecutor; this.simppManager = simppManager; this.capabilitiesVMFactory = capabilitiesVMFactory; this.managedConnectionFactory = managedConnectionFactory; this.queryUnicaster = queryUnicaster; this.socketsManager = socketsManager; this.connectionServices = connectionServices; this.nodeAssigner = nodeAssigner; this.ipFilter = ipFilter; this.connectionCheckerManager = connectionCheckerManager; this.pingRequestFactory = pingRequestFactory; this.networkInstanceUtils = networkInstanceUtils; Version v = null; try { v = new Version("4.16.6"); } catch (VersionFormatException impossible){}; lastGoodVersion = v; } @Inject void register(org.limewire.lifecycle.ServiceRegistry registry) { registry.register(this); } /** * Links the ConnectionManager up with the other back end pieces and * launches the ConnectionWatchdog and the initial ConnectionFetchers. */ public void start() { connectionDispatcher.get(). addConnectionAcceptor(this, false, ConnectionSettings.CONNECT_STRING_FIRST_WORD, "LIMEWIRE"); // schedule the Runnable that will allow us to change // the number of connections we're shooting for if // we're idle. if(SystemUtils.supportsIdleTime()) { backgroundExecutor.scheduleWithFixedDelay(new Runnable() { public void run() { setPreferredConnections(); } }, 1000, 1000, TimeUnit.MILLISECONDS); } // send new capabilities when simpp updates. simppManager.get().addListener(new SimppListener() { public void simppUpdated(int newVersion) { capabilitiesVMFactory.updateCapabilities(); sendUpdatedCapabilities(); } }); // return any filtered results to the hostcatcher. addEventListener(new ConnectionLifecycleListener() { public void handleConnectionLifecycleEvent(ConnectionLifecycleEvent evt) { if (evt.isConnectedEvent()) { List<Endpoint> filtered = new ArrayList<Endpoint>(); synchronized(ConnectionManagerImpl.this) { for (List<Endpoint> l : classCNetworks.values()) { filtered.addAll(l); l.clear(); } } for (Endpoint e : filtered) hostCatcher.get().add(e, false); } } }); } public String getServiceName() { return org.limewire.i18n.I18nMarker.marktr("Connection Management"); } public void initialize() {} public void stop() { disconnect(false); } /** * Create a new connection, allowing it to initialize and loop for messages on a new thread. */ public void createConnectionAsynchronously(String hostname, int portnum, ConnectType type) { RoutedConnection mc = managedConnectionFactory.createRoutedConnection(hostname, portnum, type); try { initializeExternallyGeneratedConnection(mc, new IncomingGNetObserver(mc)); } catch(IOException iox) { mc.close(); // ensure it's closed. } } public void acceptConnection(String word, Socket socket) { if (word.equals(ConnectionSettings.CONNECT_STRING_FIRST_WORD) || (ConnectionSettings.CONNECT_STRING.isDefault() && word.equals("LIMEWIRE"))) { acceptConnection(socket); } } /** * Create an incoming connection. * * If the connection can support asynchronous messaging, this method will return * immediately. Otherwise, this will block forever while the connection handshakes * and then loops for messages (it will return when the connection dies). */ public void acceptConnection(Socket socket) { RoutedConnection connection = managedConnectionFactory.createRoutedConnection(socket); GnetConnectObserver listener = new IncomingGNetObserver(connection); try { initializeExternallyGeneratedConnection(connection, listener); } catch (IOException e) { connection.close(); return; } } /** * Removes the specified connection from currently active connections, also * removing this connection from routing tables and modifying active * connection fetchers accordingly. * * @param mc the <tt>RoutedConnection</tt> instance to remove */ public synchronized void remove(RoutedConnection mc) { // removal may be disabled for tests if(!ConnectionSettings.REMOVE_ENABLED.getValue()) return; removeInternal(mc); adjustConnectionFetchers(); } public boolean isBlocking() { return false; } /** * True if this is currently or wants to be a supernode, * otherwise false. */ public boolean isSupernode() { return isActiveSupernode() || isSupernodeCapable(); } /** Return true if we are not a private address, have been ultrapeer capable * in the past, and are not being shielded by anybody, we don't have UP * mode disabled AND we are not exclusively a DHT node. */ public boolean isSupernodeCapable() { return !networkInstanceUtils.isPrivate() && UltrapeerSettings.EVER_ULTRAPEER_CAPABLE.getValue() && !isShieldedLeaf() && !UltrapeerSettings.DISABLE_ULTRAPEER_MODE.getValue() && !isBehindProxy() && minConnectTimePassed(); } /** * @return whether the minimum time since we started trying to connect has passed */ private boolean minConnectTimePassed() { if (!UltrapeerSettings.NEED_MIN_CONNECT_TIME.getValue()) { return true; } return Math.max(0,(System.currentTimeMillis() - _connectTime)) / 1000 >= UltrapeerSettings.MIN_CONNECT_TIME.getValue(); } /** * @return if we are currently using a http or socks4/5 proxy to connect. */ public boolean isBehindProxy() { return ConnectionSettings.CONNECTION_METHOD.getValue() != ConnectionSettings.C_NO_PROXY; } /** * Tells whether or not we're actively being a supernode to anyone. */ public boolean isActiveSupernode() { return !isShieldedLeaf() && (_initializedClientConnections.size() > 0 || _initializedConnections.size() > 0); } /** * Returns true if this is a leaf node with a connection to a ultrapeer. It * is not required that the ultrapeer support query routing, though that is * generally the case. */ public boolean isShieldedLeaf() { return _shieldedConnections != 0; } /** * Returns true if this is a super node with a connection to a leaf. */ public boolean hasSupernodeClientConnection() { return getNumInitializedClientConnections() > 0; } /** * Returns whether or not this node has any available connection * slots. This is only relevant for Ultrapeers -- leaves will * always return <tt>false</tt> to this call since they do not * accept any incoming connections, at least for now. * * @return <tt>true</tt> if this node is an Ultrapeer with free * leaf or Ultrapeer connections slots, otherwise <tt>false</tt> */ public boolean hasFreeSlots() { return isSupernode() && (hasFreeUltrapeerSlots() || hasFreeLeafSlots()); } /** * Utility method for determing whether or not we have any available * Ultrapeer connection slots. If this node is a leaf, it will * always return <tt>false</tt>. * * @return <tt>true</tt> if there are available Ultrapeer connection * slots, otherwise <tt>false</tt> */ private boolean hasFreeUltrapeerSlots() { return getNumFreeNonLeafSlots() > 0; } /** * Utility method for determing whether or not we have any available * leaf connection slots. If this node is a leaf, it will * always return <tt>false</tt>. * * @return <tt>true</tt> if there are available leaf connection * slots, otherwise <tt>false</tt> */ private boolean hasFreeLeafSlots() { return getNumFreeLeafSlots() > 0; } /** * Returns whether this (probably) has a connection to the given host. This * method is currently implemented by iterating through all connections and * comparing addresses but not ports. (Incoming connections use ephemeral * ports.) As a result, this test may conservatively return true even if * this is not connected to <tt>host</tt>. Likewise, it may it mistakenly * return false if <tt>host</tt> is a multihomed system. In the future, * additional connection headers may make the test more precise. * * @return true if this is probably connected to <tt>host</tt> */ public boolean isConnectedTo(String hostName) { //A list of all connections, both initialized and //uninitialized, leaves and unrouted. for(RoutedConnection mc : getConnections()) { if (mc.getAddress().equals(hostName)) return true; } return false; } /** * Returns true if we're currently attempting to connect to a particular host. * This checks both the Ip & Port. */ public boolean isConnectingTo(IpPort host) { synchronized(this) { for(ConnectionFetcher next : _fetchers) { IpPort them = next.getIpPort(); if(them != null && host.getAddress().equals(them.getAddress()) && host.getPort() == them.getPort()) return true; } for(RoutedConnection next : _initializingFetchedConnections) { if(host.getAddress().equals(next.getAddress()) && host.getPort() == next.getPort()) return true; } return false; } } /** * @return if there is already a connection established or establishing * to a host in the same class C network as the provided host. */ private boolean attemptClassC(Endpoint host) { if (!ConnectionSettings.FILTER_CLASS_C.getValue()) return false; List<Endpoint> l = classCNetworks.get(NetworkUtils.getClassC(host.getInetAddress())); if (l == null) return false; l.add(host); return true; } @Override public int getNumFetchingConnections() { synchronized(this) { return _initializingFetchedConnections.size(); } } /** * @return the number of connections, which is greater than or equal * to the number of initialized connections. */ public int getNumConnections() { return _connections.size(); } /** * @return the number of initialized connections, which is less than or * equals to the number of connections. */ public int getNumInitializedConnections() { return _initializedConnections.size(); } /** * @return the number of initializedclient connections, which is less than * or equals to the number of connections. */ public int getNumInitializedClientConnections() { return _initializedClientConnections.size(); } /** *@return the number of initialized connections for which * isClientSupernodeConnection is true. */ public int getNumClientSupernodeConnections() { return _shieldedConnections; } /** *@return the number of ultrapeer -> ultrapeer connections. */ public synchronized int getNumUltrapeerConnections() { return ultrapeerToUltrapeerConnections(); } /** *@return the number of old unrouted connections. */ public synchronized int getNumOldConnections() { return oldConnections(); } /** * @return the number of free leaf slots. */ public int getNumFreeLeafSlots() { if (isSupernode()) return UltrapeerSettings.MAX_LEAVES.getValue() - getNumInitializedClientConnections(); else return 0; } /** * @return the number of free leaf slots that LimeWires can connect to. */ public int getNumFreeLimeWireLeafSlots() { return Math.max(0, getNumFreeLeafSlots() - Math.max(0, RESERVED_NON_LIMEWIRE_LEAVES - _nonLimeWireLeaves) ); } /** * @return the number of free non-leaf slots. */ public int getNumFreeNonLeafSlots() { return _preferredConnections - getNumInitializedConnections(); } /** * @return the number of free non-leaf slots that LimeWires can connect to. */ public int getNumFreeLimeWireNonLeafSlots() { return Math.max(0, getNumFreeNonLeafSlots() - Math.max(0, (int) (ConnectionSettings.MIN_NON_LIME_PEERS.getValue() * _preferredConnections) - _nonLimeWirePeers) - getNumLimeWireLocalePrefSlots() ); } /** * Returns true if we've made a locale-matching connection (or don't * want any at all). */ public boolean isLocaleMatched() { return !ConnectionSettings.USE_LOCALE_PREF.getValue() || _localeMatchingPeers != 0; } /** * @return the number of locale reserved slots to be filled * * An ultrapeer may not have Free LimeWire Non Leaf Slots but may still * have free slots that are reserved for locales */ public int getNumLimeWireLocalePrefSlots() { return Math.max(0, ConnectionSettings.NUM_LOCALE_PREF.getValue() - _localeMatchingPeers); } /** * Determines if we've reached our maximum number of preferred connections. */ public boolean isFullyConnected() { return _initializedConnections.size() >= _preferredConnections; } /** * Returns whether or not the client has an established connection with * another Gnutella client. * * @return <tt>true</tt> if the client is currently connected to * another Gnutella client, <tt>false</tt> otherwise */ public boolean isConnected() { return ((_initializedClientConnections.size() > 0) || (_initializedConnections.size() > 0)); } /** * Returns whether or not we are currently attempting to connect to the * network. */ public boolean isConnecting() { if(_disconnectTime != 0) return false; if(isConnected()) return false; synchronized(this) { return _fetchers.size() != 0 || _initializingFetchedConnections.size() != 0; } } /** * Takes a snapshot of the upstream and downstream bandwidth since the last * call to measureBandwidth. * @see BandwidthTracker#measureBandwidth */ public void measureBandwidth() { float upstream=0.f; float downstream=0.f; for(RoutedConnection mc : getInitializedConnections()) { mc.measureBandwidth(); upstream+=mc.getMeasuredUpstreamBandwidth(); downstream+=mc.getMeasuredDownstreamBandwidth(); } _measuredUpstreamBandwidth=upstream; _measuredDownstreamBandwidth=downstream; } /** * Returns the upstream bandwidth in kbytes/sec between the last two calls * to measureBandwidth. * @see BandwidthTracker#measureBandwidth */ public float getMeasuredUpstreamBandwidth() { return _measuredUpstreamBandwidth; } /** * Returns the downstream bandwidth in kbytes/sec between the last two calls * to measureBandwidth. * @see BandwidthTracker#measureBandwidth */ public float getMeasuredDownstreamBandwidth() { return _measuredDownstreamBandwidth; } /** * Checks if the connection received can be accepted, * based upon the type of connection (e.g. client, ultrapeer, * temporary etc). * @param c The connection we received, for which to * test if we have incoming slot. * @return true, if we have incoming slot for the connection received, * false otherwise */ private HandshakeStatus allowConnection(RoutedConnection c) { if(!c.getConnectionCapabilities().receivedHeaders()) return HandshakeStatus.NO_HEADERS; return allowConnection(c.getConnectionCapabilities().getHeadersRead(), false); } /** * Checks if the connection received can be accepted, * based upon the type of connection (e.g. client, ultrapeer, * temporary etc). * @param c The connection we received, for which to * test if we have incoming slot. * @return true, if we have incoming slot for the connection received, * false otherwise */ public HandshakeStatus allowConnectionAsLeaf(HandshakeResponse hr) { return allowConnection(hr, true); } /** * Checks if the connection received can be accepted, * based upon the type of connection (e.g. client, ultrapeer, * temporary etc). * @param c The connection we received, for which to * test if we have incoming slot. * @return true, if we have incoming slot for the connection received, * false otherwise */ public HandshakeStatus allowConnection(HandshakeResponse hr) { return allowConnection(hr, !hr.isUltrapeer()); } /** * Checks if there is any available slot of any kind. * @return true, if we have incoming slot of some kind, * false otherwise */ public boolean allowAnyConnection() { //Stricter than necessary. if (isShieldedLeaf()) return false; //Do we have normal or leaf slots? return getNumInitializedConnections() < _preferredConnections || (isSupernode() && getNumInitializedClientConnections() < UltrapeerSettings.MAX_LEAVES.getValue()); } /** * Returns true if this has slots for an incoming connection, <b>without * accounting for this' ultrapeer capabilities</b>. More specifically: * <ul> * <li>if ultrapeerHeader==null, returns true if this has space for an * unrouted old-style connection. * <li>if ultrapeerHeader.equals("true"), returns true if this has slots * for a leaf connection. * <li>if ultrapeerHeader.equals("false"), returns true if this has slots * for an ultrapeer connection. * </ul> * * <tt>useragentHeader</tt> is used to prefer LimeWire and certain trusted * vendors. <tt>outgoing</tt> is currently unused, but may be used to * prefer incoming or outgoing connections in the forward. * * @param outgoing true if this is an outgoing connection; true if incoming * @param ultrapeerHeader the value of the X-Ultrapeer header, or null * if it was not written * @param useragentHeader the value of the User-Agent header, or null if * it was not written * @return true if a connection of the given type is allowed */ public HandshakeStatus allowConnection(HandshakeResponse hr, boolean leaf) { // preferencing may not be active for testing purposes -- // just return if it's not if(!ConnectionSettings.PREFERENCING_ACTIVE.getValue()) return HandshakeStatus.OK; // If it has not said whether or not it's an Ultrapeer or a Leaf // (meaning it's an old-style connection), don't allow it. if(!hr.isLeaf() && !hr.isUltrapeer()) return HandshakeStatus.NO_X_ULTRAPEER; //Old versions of LimeWire used to prefer incoming connections over //outgoing. The rationale was that a large number of hosts were //firewalled, so those who weren't had to make extra space for them. //With the introduction of ultrapeers, this is not an issue; all //firewalled hosts become leaf nodes. Hence we make no distinction //between incoming and outgoing. // //At one point we would actively kill old-fashioned unrouted connections //for ultrapeers. Later, we preferred ultrapeers to old-fashioned //connections as follows: if the HostCatcher had marked ultrapeer pongs, //we never allowed more than DESIRED_OLD_CONNECTIONS old //connections--incoming or outgoing. // //Now we simply prefer connections by vendor, which has some of the same //effect. We use BearShare's clumping algorithm. Let N be the //keep-alive and K be RESERVED_GOOD_CONNECTIONS. (In BearShare's //implementation, K=1.) Allow any connections in for the first N-K //slots. But only allow good vendors for the last K slots. In other //words, accept a connection C if there are fewer than N connections and //one of the following is true: C is a good vendor or there are fewer //than N-K connections. With time, this converges on all good //connections. int limeAttempts = ConnectionSettings.LIME_ATTEMPTS.getValue(); // Don't allow anything if disconnected. if (!ConnectionSettings.ALLOW_WHILE_DISCONNECTED.getValue() && _preferredConnections <= 0) { return HandshakeStatus.DISCONNECTED; // If a leaf (shielded or not), check rules as such. } else if (isShieldedLeaf() || !isSupernode()) { // require ultrapeer. if(!hr.isUltrapeer()) { return HandshakeStatus.WE_ARE_LEAVES; } // If it's not good, or it's the first few attempts & not a LimeWire, // never allow it. if(!hr.isGoodUltrapeer()) { return HandshakeStatus.NOT_GOOD_UP; } else if (_connectionAttempts < limeAttempts && !hr.isLimeWire()) { return HandshakeStatus.STARTING_LIMEWIRE; // if we have slots, allow it. } else if (_shieldedConnections < _preferredConnections) { // if it matched our preference, we don't need to preference // anymore. if(checkLocale(hr.getLocalePref())) _needPref = false; // while idle, only allow LimeWire connections. if (isIdle()) { if(hr.isLimeWire()) return HandshakeStatus.OK; else return HandshakeStatus.IDLE_LIMEWIRE; } return HandshakeStatus.OK; } else { // if we were still trying to get a locale connection // and this one matches, allow it, 'cause no one else matches. // (we would have turned _needPref off if someone matched.) if(_needPref && checkLocale(hr.getLocalePref())) { _needPref = false; return HandshakeStatus.OK; } // don't allow it. return HandshakeStatus.TOO_MANY_UPS; } } else if (hr.isLeaf() || leaf) { // no leaf connections if we're a leaf. if(isShieldedLeaf() || !isSupernode()) { return HandshakeStatus.WE_ARE_LEAVES; } if(!allowUltrapeer2LeafConnection(hr)) { return HandshakeStatus.NOT_ALLOWED_LEAF; } int leaves = getNumInitializedClientConnections(); int nonLimeWireLeaves = _nonLimeWireLeaves; // Reserve RESERVED_NON_LIMEWIRE_LEAVES slots // for non-limewire leaves to ensure that the network // is well connected. if(!hr.isLimeWire()) { if( leaves < UltrapeerSettings.MAX_LEAVES.getValue() && nonLimeWireLeaves < RESERVED_NON_LIMEWIRE_LEAVES ) { return HandshakeStatus.OK; } } // Only allow good guys. if(!hr.isGoodLeaf()) { return HandshakeStatus.NOT_GOOD_LEAF; } // if we have slots, allow it. if((leaves + Math.max(0, RESERVED_NON_LIMEWIRE_LEAVES - nonLimeWireLeaves) ) < UltrapeerSettings.MAX_LEAVES.getValue()) { return HandshakeStatus.OK; } else { return HandshakeStatus.TOO_MANY_LEAF; } } else if (hr.isUltrapeer()) { // Note that this code is NEVER CALLED when we are a leaf. // As a leaf, we will allow however many ultrapeers we happen // to connect to. // Thus, we only worry about the case we're connecting to // another ultrapeer (internally or externally generated) int peers = getNumInitializedConnections(); int nonLimeWirePeers = _nonLimeWirePeers; int locale_num = 0; if(!allowUltrapeer2UltrapeerConnection(hr)) { return HandshakeStatus.NOT_ALLOWED_UP; } if(ConnectionSettings.USE_LOCALE_PREF.getValue()) { //if locale matches and we haven't satisfied the //locale reservation then we force return a true if(checkLocale(hr.getLocalePref()) && _localeMatchingPeers < ConnectionSettings.NUM_LOCALE_PREF.getValue()) { return HandshakeStatus.OK; } //this number will be used at the end to figure out //if the connection should be allowed //(the reserved slots is to make sure we have at least // NUM_LOCALE_PREF locale connections but we could have more so // we get the max) locale_num = getNumLimeWireLocalePrefSlots(); } // If it's not a LimeWire, we'll allow it up to the ratio of // MIN_NON_LIME_PEERS. If we've exceeded that ratio, we'll allow it // if it's good and is up to MAX_NON_LIME_PEERS. // If it is a LimeWire, only allow it if we still have space left for // non-limes and it's good. if(!hr.isLimeWire()) { double nonLimeRatio = ((double)nonLimeWirePeers) / _preferredConnections; if (nonLimeRatio < ConnectionSettings.MIN_NON_LIME_PEERS.getValue()) return HandshakeStatus.OK; if(!hr.isGoodUltrapeer()) { return HandshakeStatus.NOT_GOOD_UP; } else if(nonLimeRatio < ConnectionSettings.MAX_NON_LIME_PEERS.getValue()) { return HandshakeStatus.OK; } else { return HandshakeStatus.NON_LIME_RATIO; } } else { int minNonLime = (int)(ConnectionSettings.MIN_NON_LIME_PEERS.getValue() * _preferredConnections); if(!hr.isGoodUltrapeer()) { return HandshakeStatus.NOT_GOOD_UP; } else if((peers + Math.max(0,minNonLime - nonLimeWirePeers) + locale_num ) < _preferredConnections) { return HandshakeStatus.OK; } else { return HandshakeStatus.NO_LIME_SLOTS; } } } return HandshakeStatus.UNKNOWN; } /** * Utility method for determining whether or not the connection should be * allowed as an Ultrapeer<->Ultrapeer connection. We may not allow the * connection for a variety of reasons, including lack of support for * specific features that are vital for good performance, or clients of * specific vendors that are leechers or have serious bugs that make them * detrimental to the network. * * @param hr the <tt>HandshakeResponse</tt> instance containing the * connections headers of the remote host * @return <tt>true</tt> if the connection should be allowed, otherwise * <tt>false</tt> * * Default access for testing. */ boolean allowUltrapeer2UltrapeerConnection(HandshakeResponse hr) { if(hr.isLimeWire()) { return hr.getLimeVersion() == null || hr.getLimeVersion().compareTo(lastGoodVersion) >= 0; } String userAgent = hr.getUserAgent(); if(userAgent == null) return false; userAgent = userAgent.toLowerCase(Locale.US); String[] bad = ConnectionSettings.EVIL_HOSTS.get(); for(int i = 0; i < bad.length; i++) if(userAgent.indexOf(bad[i]) != -1) return false; return true; } /** * Utility method for determining whether or not the connection should be * allowed as a leaf when we're an Ultrapeer. * * @param hr the <tt>HandshakeResponse</tt> containing their connection * headers * @return <tt>true</tt> if the connection should be allowed, otherwise * <tt>false</tt> * * Default access for testing. */ static boolean allowUltrapeer2LeafConnection(HandshakeResponse hr) { if(hr.isLimeWire()) return true; String userAgent = hr.getUserAgent(); if(userAgent == null) return false; userAgent = userAgent.toLowerCase(Locale.US); String[] bad = ConnectionSettings.EVIL_HOSTS.get(); for(int i = 0; i < bad.length; i++) if(userAgent.indexOf(bad[i]) != -1) return false; return true; } /** * Returns the number of connections that are ultrapeer -> ultrapeer. * Caller MUST hold this' monitor. */ private int ultrapeerToUltrapeerConnections() { //TODO3: augment state of this if needed to avoid loop int ret=0; for(RoutedConnection mc : _initializedConnections) { if (mc.getConnectionCapabilities().isSupernodeSupernodeConnection()) ret++; } return ret; } /** Returns the number of old-fashioned unrouted connections. Caller MUST * hold this' monitor. */ private int oldConnections() { // technically, we can allow old connections. int ret = 0; for(RoutedConnection mc : _initializedConnections) { if (!mc.getConnectionCapabilities().isSupernodeConnection()) ret++; } return ret; } /** * Tells if this node thinks that more ultrapeers are needed on the * network. This method should be invoked on a ultrapeer only, as * only ultrapeer may have required information to make informed * decision. * @return true, if more ultrapeers needed, false otherwise */ public boolean supernodeNeeded() { //if more than 90% slots are full, return true if(getNumInitializedClientConnections() >= (UltrapeerSettings.MAX_LEAVES.getValue() * 0.9)){ return true; } else { //else return false return false; } } /** * Returns a list of this' initialized connections. */ public List<RoutedConnection> getInitializedConnections() { return _initializedConnections; } /** * return a list of initialized connection that matches the parameter * String loc. * create a new linkedlist to return. */ public List<RoutedConnection> getInitializedConnectionsMatchLocale(String loc) { List<RoutedConnection> matches = new LinkedList<RoutedConnection>(); for(RoutedConnection conn : _initializedConnections) { if(loc.equals(conn.getLocalePref())) matches.add(conn); } return matches; } /** * Returns a list of this' initialized connections. */ public List<RoutedConnection> getInitializedClientConnections() { return _initializedClientConnections; } /** * return a list of initialized client connection that matches the parameter * String loc. * create a new linkedlist to return. */ public List<RoutedConnection> getInitializedClientConnectionsMatchLocale(String loc) { List<RoutedConnection> matches = new LinkedList<RoutedConnection>(); for(RoutedConnection conn : _initializedClientConnections) { if(loc.equals(conn.getLocalePref())) matches.add(conn); } return matches; } /** * @return all of this' connections. */ public List<RoutedConnection> getConnections() { return _connections; } /** * Accessor for the <tt>Set</tt> of push proxies for this node. If * there are no push proxies available, or if this node is an Ultrapeer, * this will return an empty <tt>Set</tt>. * * Callers can take ownership of the returned set; the set might be immutable. * * @return a <tt>Set</tt> of push proxies with a maximum size of 4 * * TODO: should the set of pushproxy UPs be cached and updated as * connections are killed and created? */ public Set<Connectable> getPushProxies() { if (isShieldedLeaf()) { // this should be fast since leaves don't maintain a lot of // connections and the test for proxy support is cached boolean // value Set<Connectable> proxies = new StrictIpPortSet<Connectable>(); for(RoutedConnection currMC : getInitializedConnections()) { if(proxies.size() >= 4) break; if (currMC.isMyPushProxy()) { if (LOG.isDebugEnabled()) LOG.debug(currMC.getAddress() + " has version: " + currMC.getConnectionCapabilities().getUserAgent()); proxies.add(currMC); } } return proxies; } else { return Collections.emptySet(); } } /** * Sends a TCPConnectBack request to (up to) 2 connected Ultrapeers. * @returns false if no requests were sent, otherwise true. */ public boolean sendTCPConnectBackRequests() { int sent = 0; List<RoutedConnection> peers = new ArrayList<RoutedConnection>(getInitializedConnections()); Collections.shuffle(peers); for (Iterator<RoutedConnection> iter = peers.iterator(); iter.hasNext();) { RoutedConnection currMC = iter.next(); if (currMC.getConnectionCapabilities().remoteHostSupportsTCPRedirect() < 0) iter.remove(); } if (peers.size() == 1) { RoutedConnection myConn = peers.get(0); for (int i = 0; i < CONNECT_BACK_REDUNDANT_REQUESTS; i++) { // This is inside to generate a different GUID for each request. Message cb = new TCPConnectBackVendorMessage(networkManager.getPort()); myConn.send(cb); sent++; } } else { final Message cb = new TCPConnectBackVendorMessage(networkManager.getPort()); for(RoutedConnection currMC : peers) { if(sent >= 5) break; currMC.send(cb); sent++; } } return (sent > 0); } /** * Sends a UDPConnectBack request to (up to) 4 (and at least 2) * connected Ultrapeers. * @returns false if no requests were sent, otherwise true. */ public boolean sendUDPConnectBackRequests(GUID cbGuid) { int sent = 0; final Message cb = new UDPConnectBackVendorMessage(networkManager.getPort(), cbGuid); List<RoutedConnection> peers = new ArrayList<RoutedConnection>(getInitializedConnections()); Collections.shuffle(peers); for(RoutedConnection currMC : peers) { if(sent >= 5) break; if (currMC.getConnectionCapabilities().remoteHostSupportsUDPConnectBack() >= 0) { currMC.send(cb); sent++; } } return (sent > 0); } /** * Sends a QueryStatusResponse message to as many Ultrapeers as possible. * * @param */ public void updateQueryStatus(QueryStatusResponse stat) { if (isShieldedLeaf()) { // this should be fast since leaves don't maintain a lot of // connections and the test for query status response is a cached // value for(RoutedConnection currMC : getInitializedConnections()) { if (currMC.getConnectionCapabilities().remoteHostSupportsLeafGuidance() >= 0) currMC.send(stat); } } } /** * Returns the <tt>Endpoint</tt> for an Ultrapeer connected via TCP, * if available. * * @return the <tt>Endpoint</tt> for an Ultrapeer connected via TCP if * there is one, otherwise returns <tt>null</tt> */ public Endpoint getConnectedGUESSUltrapeer() { for(RoutedConnection connection : _initializedConnections) { if(connection.getConnectionCapabilities().isSupernodeConnection() && connection.getConnectionCapabilities().isGUESSUltrapeer()) { return new Endpoint(connection.getInetAddress().getAddress(), connection.getPort()); } } return null; } /** Returns a <tt>List<tt> of Ultrapeers connected via TCP that are GUESS * enabled. * * @return A non-null List of GUESS enabled, TCP connected Ultrapeers. The * are represented as ManagedConnections. */ public List<RoutedConnection> getConnectedGUESSUltrapeers() { List<RoutedConnection> retList = new ArrayList<RoutedConnection>(); for(RoutedConnection connection : _initializedConnections) { if(connection.getConnectionCapabilities().isSupernodeConnection() && connection.getConnectionCapabilities().isGUESSUltrapeer()) retList.add(connection); } return retList; } /** * Adds an initializing connection. * Should only be called from a thread that has this' monitor. * This is called from initializeExternallyGeneratedConnection * and initializeFetchedConnection, both times from within a * synchronized(this) block. */ private void connectionInitializing(RoutedConnection c) { //REPLACE _connections with the list _connections+[c] List<RoutedConnection> newConnections=new ArrayList<RoutedConnection>(_connections); newConnections.add(c); _connections = Collections.unmodifiableList(newConnections); try { int classC = NetworkUtils.getClassC(InetAddress.getByName(c.getAddress())); List<Endpoint> l = classCNetworks.get(classC); if (l == null) classCNetworks.put(classC,new ArrayList<Endpoint>()); } catch (UnknownHostException uhe) { LOG.info("Exception while initializing connection", uhe); // this will cause the connection to fail, ignore } } /** * Adds an incoming connection to the list of connections. Note that * the incoming connection has already been initialized before * this method is invoked. * Should only be called from a thread that has this' monitor. * This is called from initializeExternallyGeneratedConnection, for * incoming connections * * Default access for testing. */ public void connectionInitializingIncoming(RoutedConnection c) { connectionInitializing(c); } /** * Marks a connection fully initialized, but only if that connection wasn't * removed from the list of open connections during its initialization. * Should only be called from a thread that has this' monitor. * * Default access for testing. */ public boolean connectionInitialized(RoutedConnection c) { if(_connections.contains(c)) { // Double-check that we haven't improperly allowed // this connection. It is possible that, because of race-conditions, // we may have allowed both a 'Peer' and an 'Ultrapeer', or an 'Ultrapeer' // and a leaf. That'd 'cause undefined results if we allowed it. if(!allowInitializedConnection(c)) { removeInternal(c); return false; } //update the appropriate list of connections if(!c.getConnectionCapabilities().isSupernodeClientConnection()){ //REPLACE _initializedConnections with the list //_initializedConnections+[c] List<RoutedConnection> newConnections=new ArrayList<RoutedConnection>(_initializedConnections); newConnections.add(c); _initializedConnections = Collections.unmodifiableList(newConnections); if(c.getConnectionCapabilities().isClientSupernodeConnection()) { killPeerConnections(); // clean up any extraneus peer conns. _shieldedConnections++; } if(!c.getConnectionCapabilities().isLimeWire()) _nonLimeWirePeers++; if(checkLocale(c.getLocalePref())) _localeMatchingPeers++; } else { //REPLACE _initializedClientConnections with the list //_initializedClientConnections+[c] List<RoutedConnection> newConnections =new ArrayList<RoutedConnection>(_initializedClientConnections); newConnections.add(c); _initializedClientConnections = Collections.unmodifiableList(newConnections); if(!c.getConnectionCapabilities().isLimeWire()) _nonLimeWireLeaves++; } // do any post-connection initialization that may involve sending. c.sendPostInitializeMessages(); // sending the ping request. sendInitialPingRequest(c); return true; } return false; } /** * Like allowConnection, but more more strict. * In addition to allowConnection, this checks to see if the connection * is a leaf, and if so only allows it if we said we're it's supernode. * It also makes sure that we don't have any duplicate connections to this * particular host. (Duplicate connections are checked by IP address and * 'listen port', if a listen port was specified.) * * @return whether the connection should be allowed */ private boolean allowInitializedConnection(RoutedConnection c) { if ((isShieldedLeaf() || !isSupernode()) && !c.getConnectionCapabilities().isClientSupernodeConnection()) return false; List<RoutedConnection> connections = getConnections(); int listenPort = c.getListeningPort(); String addr = c.getAddress(); for(int i = 0; i < connections.size(); i++ ) { RoutedConnection mc = connections.get(i); if(mc == c) continue; if(!ConnectionSettings.ALLOW_DUPLICATE.getValue() && addr.equals(mc.getAddress())) { int mcLP = mc.getListeningPort(); // If either side didn't advertise a listening port, // or both did and they're the same, then because the // addresses are also equal, disallow it. // -1 == unknown, 0 == connecting w/o NIOSocket if(listenPort == -1 || listenPort == 0 || mcLP == -1 || mcLP == 0 || mcLP == listenPort) return false; } } return allowConnection(c.getConnectionCapabilities().getHeadersRead()).isAcceptable(); } /** * removes any supernode->supernode connections */ private void killPeerConnections() { List<RoutedConnection> conns = _initializedConnections; for(RoutedConnection con : conns) { if (con.getConnectionCapabilities().isSupernodeSupernodeConnection()) removeInternal(con); } } /** * Iterates over all the connections and sends the updated CapabilitiesVM * down every one of them. */ public void sendUpdatedCapabilities() { for(Connection c : getInitializedConnections()) c.sendUpdatedCapabilities(); for(Connection c : getInitializedClientConnections()) c.sendUpdatedCapabilities(); } /** * Disconnects from the network. Closes all connections and sets * the number of connections to zero. * * @param willTryToReconnect Whether or not this is only a temporary disconnection */ public synchronized void disconnect(boolean willTryToReconnect) { if(_disconnectTime == 0) { long averageUptime = getCurrentAverageUptime(); int totalConnections = Math.max(1, ApplicationSettings.TOTAL_CONNECTIONS.getValue() + 1); long totalConnectTime = averageUptime * totalConnections; ApplicationSettings.TOTAL_CONNECTION_TIME.setValue(totalConnectTime); ApplicationSettings.TOTAL_CONNECTIONS.setValue(totalConnections); ApplicationSettings.AVERAGE_CONNECTION_TIME.setValue(averageUptime); } _disconnectTime = System.currentTimeMillis(); _connectTime = Long.MAX_VALUE; _preferredConnections = 0; adjustConnectionFetchers(); // kill them all //2. Remove all connections. for(RoutedConnection c : getConnections()) { remove(c); //add the endpoint to hostcatcher if (c.getConnectionCapabilities().isSupernodeConnection()) { //add to catcher with the locale info. ExtendedEndpoint ee = new ExtendedEndpoint(c.getInetAddress().getHostAddress(), c.getPort(), c.getLocalePref()); ee.setTLSCapable(c.isTLSCapable()); hostCatcher.get().add(ee, true); } } if(!willTryToReconnect) { dispatchEvent(new ConnectionLifecycleEvent(ConnectionManagerImpl.this, ConnectionLifecycleEventType.DISCONNECTED, null)); } } /** * Returns this node's average connection time - in ms - including the current session. * */ public synchronized long getCurrentAverageUptime() { long currentAverage = 0; long now = System.currentTimeMillis(); long sessionTime = Math.max(0,now - _connectTime); //in ms int totalConnections = ApplicationSettings.TOTAL_CONNECTIONS.getValue(); if(sessionTime != 0) { //else don't count current session totalConnections+=1; } long totalConnectTime = Math.max(0, ApplicationSettings.TOTAL_CONNECTION_TIME.getValue() + sessionTime); currentAverage = totalConnectTime/Math.max(1,totalConnections); return currentAverage; } /** * Connects to the network. Ensures the number of messaging connections * is non-zero and recontacts the pong server as needed. */ public synchronized void connect() { // Reset the disconnect time to be a long time ago. _disconnectTime = 0; _connectTime = System.currentTimeMillis(); // Ignore this call if we're already connected // or not initialized yet. if(isConnected() || hostCatcher == null) { return; } _connectionAttempts = 0; _lastConnectionCheck = 0; _lastSuccessfulConnect = 0; numTCPConnectBacksLeft = MAX_TCP_CONNECT_BACK_ATTEMPTS; numUDPConnectBacksLeft = MAX_UDP_CONNECT_BACK_ATTEMPTS; // Set the number of connections we want to maintain setPreferredConnections(); // Tell the host catcher to start pinging hostCatcher.get().connect(); } /** * Sends the initial ping request to a newly initialized connection. The * ttl of the PingRequest will be 1 if we don't need any connections. * Otherwise, the ttl = max ttl. */ private void sendInitialPingRequest(RoutedConnection connection) { if(connection.getConnectionCapabilities().supportsPongCaching()) return; //We need to compare how many connections we have to the keep alive to //determine whether to send a broadcast ping or a handshake ping, //initially. However, in this case, we can't check the number of //connection fetchers currently operating, as that would always then //send a handshake ping, since we're always adjusting the connection //fetchers to have the difference between keep alive and num of //connections. PingRequest pr; if (getNumInitializedConnections() >= _preferredConnections) pr = pingRequestFactory.createPingRequest((byte)1); else pr = pingRequestFactory.createPingRequest((byte)4); connection.send(pr); } /** * An unsynchronized version of remove, meant to be used when the monitor * is already held. This version does not kick off ConnectionFetchers; * only the externally exposed version of remove does that. */ private void removeInternal(RoutedConnection c) { // 1a) Remove from the initialized connections list and clean up the // stuff associated with initialized connections. For efficiency // reasons, this must be done before (2) so packets are not forwarded // to dead connections (which results in lots of thrown exceptions). if(!c.getConnectionCapabilities().isSupernodeClientConnection()){ int i=_initializedConnections.indexOf(c); if (i != -1) { //REPLACE _initializedConnections with the list //_initializedConnections-[c] List<RoutedConnection> newConnections=new ArrayList<RoutedConnection>(); newConnections.addAll(_initializedConnections); newConnections.remove(c); _initializedConnections = Collections.unmodifiableList(newConnections); //maintain invariant if(c.getConnectionCapabilities().isClientSupernodeConnection()) _shieldedConnections--; if(!c.getConnectionCapabilities().isLimeWire()) _nonLimeWirePeers--; if(checkLocale(c.getLocalePref())) _localeMatchingPeers--; } }else{ //check in _initializedClientConnections int i=_initializedClientConnections.indexOf(c); if (i != -1) { //REPLACE _initializedClientConnections with the list //_initializedClientConnections-[c] List<RoutedConnection> newConnections=new ArrayList<RoutedConnection>(); newConnections.addAll(_initializedClientConnections); newConnections.remove(c); _initializedClientConnections = Collections.unmodifiableList(newConnections); if(!c.getConnectionCapabilities().isLimeWire()) _nonLimeWireLeaves--; } } // 1b) Remove from the all connections list and clean up the // stuff associated all connections int i=_connections.indexOf(c); if (i != -1) { //REPLACE _connections with the list _connections-[c] List<RoutedConnection> newConnections=new ArrayList<RoutedConnection>(_connections); newConnections.remove(c); _connections = Collections.unmodifiableList(newConnections); } // 1c) Remove from list of class C networks and return bypassed to catcher try { List<Endpoint> l = classCNetworks.remove(NetworkUtils.getClassC(InetAddress.getByName(c.getAddress()))); if (l != null) { for (Endpoint ip : l) hostCatcher.get().add(ip, false); } } catch (UnknownHostException ignore){} // 2) Ensure that the connection is closed. This must be done before // step (3) to ensure that dead connections are not added to the route // table, resulting in dangling references. c.close(); // 4) Notify the listener dispatchEvent(new ConnectionLifecycleEvent(ConnectionManagerImpl.this, ConnectionLifecycleEventType.CONNECTION_CLOSED, c)); // 5) Clean up Unicaster queryUnicaster.get().purgeQuery(c); } /** * Stabilizes connections by removing extraneous ones. * * This will remove the connections that we've been connected to * for the shortest amount of time. */ private synchronized void stabilizeConnections() { while(getNumInitializedConnections() > _preferredConnections) { RoutedConnection newest = null; for(RoutedConnection c : _initializedConnections) { // first see if this is a non-limewire connection and cut it off // unless it is our only connection left if (!c.getConnectionCapabilities().isLimeWire()) { newest = c; break; } if(newest == null || c.getConnectionTime() > newest.getConnectionTime()) newest = c; } if(newest != null) remove(newest); } adjustConnectionFetchers(); } /** * Starts or stops connection fetchers to maintain the invariant that numConnections + numFetchers >= * _preferredConnections * * _preferredConnections - numConnections - numFetchers is called the need. * This method is called whenever the need changes: * 1. setPreferredConnections() -- _preferredConnections changes * 2. remove(Connection) -- numConnections drops. * 3. initializeExternallyGeneratedConnection() -- numConnections rises. * 4. initialization error in initializeFetchedConnection() -- numConnections drops when removeInternal is called. * Note that adjustConnectionFetchers is not called when a connection is successfully fetched from the host catcher. * numConnections rises, but numFetchers drops, so need is unchanged. * * Only call this method when the monitor is held. */ private void adjustConnectionFetchers() { if(ConnectionSettings.USE_LOCALE_PREF.getValue()) { startDedicatedLocaleFetcher(); } int goodConnections = getNumInitializedConnections(); int neededConnections = _preferredConnections - goodConnections; //Now how many fetchers do we need? To increase parallelism, we //allocate 3 fetchers per connection, but no more than 10 fetchers. //(Too much parallelism increases chance of simultaneous connects, //resulting in too many connections.) Note that we assume that all //connections being fetched right now will become ultrapeers. int multiple; // The end result of the following logic, assuming _preferredConnections // is 32 for Ultrapeers, is: // When we have 22 active peer connections, we fetch // (27-current)*1 connections. // All other times, for Ultrapeers, we will fetch // (32-current)*3, up to a maximum of 20. // For leaves, assuming they maintin 4 Ultrapeers, // we will fetch (4-current)*2 connections. // If we have not accepted incoming, fetch 3 times // as many connections as we need. // We must also check if we're actively being a Ultrapeer because // it's possible we may have turned off acceptedIncoming while // being an Ultrapeer. if( !networkManager.acceptedIncomingConnection() && !isActiveSupernode() ) { multiple = 3; } // Otherwise, if we're not ultrapeer capable, // or have not become an Ultrapeer to anyone, // also fetch 3 times as many connections as we need. // It is critical that active ultrapeers do not use a multiple of 3 // without reducing neededConnections, otherwise LimeWire would // continue connecting and rejecting connections forever. else if( !isSupernode() || getNumUltrapeerConnections() == 0 ) { multiple = 3; } // Otherwise (we are actively Ultrapeering to someone) // If we needed more than connections, still fetch // 2 times as many connections as we need. // It is critical that 10 is greater than RESERVED_NON_LIMEWIRE_PEERS, // else LimeWire would try connecting and rejecting connections forever. else if( neededConnections > 10 ) { multiple = 2; } // Otherwise, if we need less than 10 connections (and we're an Ultrapeer), // decrement the amount of connections we need by 5, // leaving 5 slots open for newcomers to use, // and decrease the rate at which we fetch to // 1 times the amount of connections. else { multiple = 1; neededConnections -= 5 + ConnectionSettings.MIN_NON_LIME_PEERS.getValue() * _preferredConnections; } int need = Math.min(10, multiple*neededConnections) - _fetchers.size() - _initializingFetchedConnections.size(); // do not open more sockets than we can need = Math.min(need, socketsManager.getNumAllowedSockets()); // Build up lists of what we need to connect to & remove from connecting. // Start connection fetchers as necessary List<ConnectionFetcher> fetchers = Collections.emptyList(); if (need > 0) { fetchers = new ArrayList<ConnectionFetcher>(need); while (need > 0) { // This kicks off the thread for the fetcher ConnectionFetcher fetcher = new ConnectionFetcher(); fetchers.add(fetcher); need--; } _fetchers.addAll(fetchers); dispatchEvent(new ConnectionLifecycleEvent(ConnectionManagerImpl.this, ConnectionLifecycleEventType.CONNECTING, null)); } // Stop ConnectionFetchers as necessary, but it's possible there // aren't enough fetchers to stop. In this case, close some of the // connections started by ConnectionFetchers. int lastFetcherIndex = _fetchers.size(); List<Object> extras = new ArrayList<Object>(); while((need < 0) && (lastFetcherIndex > 0)) { ConnectionFetcher fetcher = _fetchers.remove(--lastFetcherIndex); need++; extras.add(fetcher); } int lastInitializingConnectionIndex = _initializingFetchedConnections.size(); while((need < 0) && (lastInitializingConnectionIndex > 0)) { RoutedConnection connection = _initializingFetchedConnections.remove(--lastInitializingConnectionIndex); need++; extras.add(connection); } // Now connect'm. for(int i = fetchers.size() - 1; i >= 0; i--) { ConnectionFetcher fetcher = fetchers.remove(i); fetcher.connect(); } // And delete extras. for(int i = extras.size() - 1; i >= 0; i--) { Object next = extras.remove(i); if(next instanceof ConnectionFetcher) { ((ConnectionFetcher)next).stopConnecting(); } else { removeInternal((RoutedConnection)next); } } } /** Starts the dedicated locale ConnectionFetcher, if necessary */ private void startDedicatedLocaleFetcher() { // if it's a leaf and locale preferencing is on // we will create a dedicated preference fetcher // that tries to fetch a connection that matches the // clients locale if (connectionServices.isShieldedLeaf() && _needPref && !_needPrefInterrupterScheduled && _dedicatedPrefFetcher == null) { _dedicatedPrefFetcher = new ConnectionFetcher(true); _dedicatedPrefFetcher.connect(); Runnable interrupted = new Runnable() { public void run() { synchronized (ConnectionManagerImpl.this) { // always finish once this runs. _needPref = false; if (_dedicatedPrefFetcher == null) return; _dedicatedPrefFetcher.stopConnecting(); _dedicatedPrefFetcher = null; } } }; _needPrefInterrupterScheduled = true; // shut off this guy if he didn't have any luck backgroundExecutor.schedule(interrupted, 15 * 1000, TimeUnit.MILLISECONDS); } } /** * Begins initializing fetched connections. * This will maintain all class invariants and then wait to hear * back from the ConnectionFetcher after connecting succeeds or fails. */ private void initializeFetchedConnection(RoutedConnection mc, ConnectionFetcher fetcher) { synchronized(this) { if(fetcher.isPrematurelyStopped()) { fetcher.finish(); return; } _initializingFetchedConnections.add(mc); if(fetcher == _dedicatedPrefFetcher) _dedicatedPrefFetcher = null; else _fetchers.remove(fetcher); connectionInitializing(mc); // No need to adjust connection fetchers here. We haven't changed // the need for connections; we've just replaced a ConnectionFetcher // with a Connection. } dispatchEvent(new ConnectionLifecycleEvent(ConnectionManagerImpl.this, ConnectionLifecycleEventType.CONNECTION_INITIALIZING, mc)); try { mc.initialize(fetcher); } catch(IOException e) { cleanupBrokenFetchedConnection(mc); if(LOG.isInfoEnabled()) LOG.info("Exception initializing connection to " + mc.getAddress() + ":" + mc.getPort(), e); } } /** * Cleans up references to the given connection. * * This removes the now-dead connection from the list of initializing fetched * connections, ensures that the connection is closed and nothing can * be routed to it, adjusts connection fetchers so as to spark a new fetcher * if needed, and processes the headers of the connection for addition to the local * hostcache. * @param mc */ private void cleanupBrokenFetchedConnection(RoutedConnection mc) { synchronized (this) { _initializingFetchedConnections.remove(mc); removeInternal(mc); // We've removed a connection, so the need for connections went // up. We may need to launch a fetcher. adjustConnectionFetchers(); } processConnectionHeaders(mc); } /** * Processes the headers received during connection handshake and updates * itself with any useful information contained in those headers. * Also may change its state based upon the headers. * @param headers The headers to be processed * @param connection The connection on which we received the headers */ private void processConnectionHeaders(Connection connection){ if(!connection.getConnectionCapabilities().receivedHeaders()) { return; } //get the connection headers Properties headers = connection.getConnectionCapabilities().getHeadersRead().props(); //return if no headers to process if(headers == null) return; //update the addresses in the host cache (in case we received some //in the headers) updateHostCache(connection.getConnectionCapabilities().getHeadersRead()); //get remote address. If the more modern "Listen-IP" header is //not included, try the old-fashioned "X-My-Address". String remoteAddress = headers.getProperty(HeaderNames.LISTEN_IP); if (remoteAddress==null) remoteAddress = headers.getProperty(HeaderNames.X_MY_ADDRESS); //set the remote port if not outgoing connection (as for the outgoing //connection, we already know the port at which remote host listens) if((remoteAddress != null) && (!connection.isOutgoing())) { int colonIndex = remoteAddress.indexOf(':'); if(colonIndex == -1) return; colonIndex++; if(colonIndex > remoteAddress.length()) return; try { int port = Integer.parseInt(remoteAddress.substring(colonIndex).trim()); if(NetworkUtils.isValidPort(port)) { // for incoming connections, set the port based on what it's // connection headers say the listening port is connection.setListeningPort(port); } } catch(NumberFormatException e){ // should nothappen though if the other client is well-coded } } } /** * Returns true if this can safely switch from Ultrapeer to leaf mode. * Typically this means that we are an Ultrapeer and have no leaf * connections. * * @return <tt>true</tt> if we will allow ourselves to become a leaf, * otherwise <tt>false</tt> */ public boolean allowLeafDemotion() { _leafTries++; if (UltrapeerSettings.FORCE_ULTRAPEER_MODE.getValue() || isActiveSupernode()) return false; else if(nodeAssigner.get().isTooGoodUltrapeerToPassUp() && _leafTries < _demotionLimit) return false; else return true; } /** * Notifies the connection manager that it should attempt to become an * Ultrapeer. If we already are an Ultrapeer, this will be ignored. * * @param demotionLimit the number of attempts by other Ultrapeers to * demote us to a leaf that we should allow before giving up in the * attempt to become an Ultrapeer */ public void tryToBecomeAnUltrapeer(int demotionLimit) { if(isSupernode()) return; _demotionLimit = demotionLimit; _leafTries = 0; disconnect(true); connect(); } /** * Adds the X-Try-Ultrapeer hosts from the connection headers to the * host cache. * * @param headers the connection headers received */ private void updateHostCache(HandshakeResponse headers) { if(!headers.hasXTryUltrapeers()) return; //get the ultrapeers, and add those to the host cache String hostAddresses = headers.getXTryUltrapeers(); //tokenize to retrieve individual addresses StringTokenizer st = new StringTokenizer(hostAddresses, Constants.ENTRY_SEPARATOR); List<Endpoint> hosts = new ArrayList<Endpoint>(st.countTokens()); while(st.hasMoreTokens()){ String address = st.nextToken().trim(); try { hosts.add(new Endpoint(address)); } catch(IllegalArgumentException iae){ continue; } } hostCatcher.get().add(ConnectionSettings.FILTER_CLASS_C.getValue() ? NetworkUtils.filterOnePerClassC(hosts) : hosts); ; } /** * Initializes an outgoing connection created by createConnection or any * incomingConnection. If this is an incoming connection and there are no * slots available, rejects it and throws IOException. * * @throws IOException on failure. No cleanup is necessary if this happens. */ private void initializeExternallyGeneratedConnection(RoutedConnection c, GnetConnectObserver observer) throws IOException { //For outgoing connections add it to the GUI and the fetcher lists now. //For incoming, we'll do this below after checking incoming connection //slots. This keeps reject connections from appearing in the GUI, as //well as improving performance slightly. if (c.isOutgoing()) { synchronized(this) { connectionInitializing(c); // We've added a connection, so the need for connections went // down. adjustConnectionFetchers(); } dispatchEvent(new ConnectionLifecycleEvent(ConnectionManagerImpl.this, ConnectionLifecycleEventType.CONNECTION_INITIALIZING, c)); } try { c.initialize(observer); } catch(IOException e) { cleanupBrokenExternallyGeneratedConnection(c); if(LOG.isInfoEnabled()) LOG.info("Exception initializing connection to " + c.getAddress() + ":" + c.getPort(), e); throw e; } } /** * Cleans up a connection that couldn't be initialized. * @param c */ private void cleanupBrokenExternallyGeneratedConnection(RoutedConnection c) { remove(c); processConnectionHeaders(c); } /** * Completes the process of initializing an externally generated connection. * * @param c * @throws IOException * * @return true if the connection should continue, false if it was closed */ private boolean completeInitializeExternallyGeneratedConnection(RoutedConnection c) throws IOException { processConnectionHeaders(c); // If there's not space for the connection, destroy it. // It really should have been destroyed earlier, but this is just in case. if (!c.isOutgoing() && !allowConnection(c).isAcceptable()) { // No need to remove, since it hasn't been added to any lists. throw new IOException("No space for connection"); } // For incoming connections, add it to the GUI. For outgoing connections // this was done at the top of the method. See note there. if (!c.isOutgoing()) { synchronized (this) { connectionInitializingIncoming(c); // We've added a connection, so the need for connections went // down. adjustConnectionFetchers(); } dispatchEvent(new ConnectionLifecycleEvent(ConnectionManagerImpl.this, ConnectionLifecycleEventType.CONNECTION_INITIALIZING, c)); } return completeConnectionInitialization(c, false); } /** * Performs the steps necessary to complete connection initialization. * * @param mc the <tt>RoutedConnection</tt> to finish initializing * @param fetched Specifies whether or not this connection is was fetched by a connection fetcher. If so, this * removes that connection from the list of fetched connections being initialized, keeping the connection * fetcher data in sync * * @return true if the connection should continue, false if it was closed */ private boolean completeConnectionInitialization(RoutedConnection mc, boolean fetched) { synchronized(this) { if(fetched) { _initializingFetchedConnections.remove(mc); } // If the connection was killed while initializing, we shouldn't // announce its initialization boolean connectionOpen = connectionInitialized(mc); if(connectionOpen) { dispatchEvent(new ConnectionLifecycleEvent(ConnectionManagerImpl.this, ConnectionLifecycleEventType.CONNECTION_INITIALIZED, mc)); setPreferredConnections(); if (_initializedConnections.size() >= getPreferredConnectionCount()) { _lastFullConnectTime = System.currentTimeMillis(); dispatchEvent(new ConnectionLifecycleEvent(ConnectionManagerImpl.this, ConnectionLifecycleEventType.CONNECTED, mc)); } } return connectionOpen; } } /** * Gets the number of preferred connections to maintain. */ public int getPreferredConnectionCount() { return _preferredConnections; } /** * Determines if we're attempting to maintain the idle connection count. */ public boolean isConnectionIdle() { return _preferredConnections == ConnectionSettings.IDLE_CONNECTIONS.getValue(); } /** * Sets the maximum number of connections we'll maintain. */ private void setPreferredConnections() { // if we're disconnected, do nothing. if(!ConnectionSettings.ALLOW_WHILE_DISCONNECTED.getValue() && _disconnectTime != 0) return; int oldPreferred = _preferredConnections; if(isSupernode()) _preferredConnections = ConnectionSettings.NUM_CONNECTIONS.getValue(); else if(isIdle()) _preferredConnections = ConnectionSettings.IDLE_CONNECTIONS.getValue(); else _preferredConnections = PREFERRED_CONNECTIONS_FOR_LEAF; if(oldPreferred != _preferredConnections) stabilizeConnections(); } /** * Determines if we're idle long enough to change the number of connections. */ private boolean isIdle() { return SystemUtils.getIdleTime() >= MINIMUM_IDLE_TIME; } /** * Runs standard calls that should be made whenever a connection is fully * established and should wait for messages. * * @param conn the <tt>RoutedConnection</tt> instance to start * @throws <tt>IOException</tt> if there is an excpetion while looping * for messages */ private void startConnection(RoutedConnection conn) throws IOException { if(conn.getConnectionCapabilities().isGUESSUltrapeer()) queryUnicaster.get().addUnicastEndpoint(conn.getInetAddress(), conn.getPort()); if(LOG.isDebugEnabled()) LOG.debug("Looping for messages with conn: " + conn); // this can throw IOException conn.startMessaging(); } /** * Asynchronous GnetConnectObserver for externally generated connections. * Not as robust as ConnectionFetcher because less accounting is needed. */ private class IncomingGNetObserver implements GnetConnectObserver { private final RoutedConnection connection; IncomingGNetObserver(RoutedConnection connection) { this.connection = connection; } public void handleConnect() { if(LOG.isDebugEnabled()) LOG.debug("Completing IncomingGNetObserver.handleConnect for: " + connection); try { if(completeInitializeExternallyGeneratedConnection(connection)) startConnection(connection); } catch(IOException ignored) { LOG.warn("Failed to complete initialization", ignored); } } public void handleBadHandshake() { shutdown(); } public void handleNoGnutellaOk(int code, String msg) { shutdown(); } public void shutdown() { LOG.debug("Shutting down IncomingGNetobserver for: " + connection); cleanupBrokenExternallyGeneratedConnection(connection); } } /** * Asynchronously fetches a connection from hostcatcher, then does * then initialization and message loop. * * The ConnectionFetcher is responsible for recording its instantiation * by adding itself to the fetchers list. It is responsible for recording * its death by removing itself from the fetchers list only if fetching proceeded * beyond the 'connect' stage. That is, if a connect attempt is performed and failed * for any reason, this must clean itself up. */ private class ConnectionFetcher implements GnetConnectObserver, HostCatcher.EndpointObserver { // set if this connectionfetcher is a preferencing fetcher private final boolean _pref; private volatile RoutedConnection connection; private volatile Endpoint endpoint; private volatile boolean stoppedEarly = false; public ConnectionFetcher() { this(false); } public ConnectionFetcher(boolean pref) { _pref = pref; } IpPort getIpPort() { if(connection != null) return connection; else if(endpoint != null) return endpoint; else return null; } /** Starts the process of connecting to an arbitary endpoint. */ public void connect() { hostCatcher.get().getAnEndpoint(this); } /** * Marks this fetcher as not wanting to connect. * It is entirely possible that this fetcher has proceeded to connect * already. If that's the case, this call essentially does nothing. */ public void stopConnecting() { stoppedEarly = true; hostCatcher.get().removeEndpointObserver(this); } /** Returns whether or not we were told to stop early. */ public boolean isPrematurelyStopped() { return stoppedEarly; } /** Does nothing right now. */ public void finish() { } /** Returns true if we are able to make a connection attempt to this host. */ private boolean isConnectableHost(IpPort host) { return ipFilter.get().allow(host.getAddress()) && !isConnectedTo(host.getAddress()) && !isConnectingTo(host); } /** Callback that an endpoint is available for connecting. */ public void handleEndpoint(Endpoint incoming) { assert incoming != null; // If this was an invalid endpoint, try again. while(!isConnectableHost(incoming) || attemptClassC(incoming)) { if(LOG.isInfoEnabled()) LOG.info("Ignoring unconnectable host: " + incoming); incoming = hostCatcher.get().getAnEndpointImmediate(this); if(incoming == null) { LOG.info("No hosts available, waiting on a new one"); return; // if we didn't get one immediate, the callback is scheduled } } if(LOG.isInfoEnabled()) LOG.info("Starting fetch for connectable host: " + incoming); this.endpoint = incoming; ConnectType type = endpoint.isTLSCapable() && networkManager.isOutgoingTLSEnabled() ? ConnectType.TLS : ConnectType.PLAIN; LOG.debugf("connecting to {0}, with connect type {1}", incoming, type); connection = managedConnectionFactory.createRoutedConnection(endpoint.getAddress(), endpoint.getPort(), type); connection.setLocalePreferencing(_pref); doConnectionCheck(); _connectionAttempts++; initializeFetchedConnection(connection, this); } /** Callback that handshaking has succeeded and we're all connected and ready. */ public void handleConnect() { if(completeConnectionInitialization(connection, true)) { processConnectionHeaders(connection); _lastSuccessfulConnect = System.currentTimeMillis(); hostCatcher.get().doneWithConnect(endpoint, true); if(_pref) _needPref = false; try { startConnection(connection); } catch(IOException ignored) {} } else { hostCatcher.get().doneWithConnect(endpoint, false); } } /** Callback that a connect failed. */ public void shutdown() { cleanupBrokenFetchedConnection(connection); hostCatcher.get().doneWithConnect(endpoint, false); hostCatcher.get().expireHost(endpoint); } /** Callback that handshaking failed. */ public void handleBadHandshake() { shutdown(); } /** Callback that connecting worked, but we got something other than a Gnutella OK */ public void handleNoGnutellaOk(int code, String msg) { cleanupBrokenFetchedConnection(connection); _lastSuccessfulConnect = System.currentTimeMillis(); if (code == HandshakeResponse.LOCALE_NO_MATCH) { // Failures because of locale aren't really a failure. hostCatcher.get().add(endpoint, true, connection.getLocalePref()); } else { hostCatcher.get().doneWithConnect(endpoint, false); hostCatcher.get().putHostOnProbation(endpoint); } } /** Checks to see if we need to check for a live connection. */ private void doConnectionCheck() { // If we've been trying to connect for awhile, check to make // sure the user's internet connection is live. We only do // this if we're not already connected, have not made any // successful connections recently, and have not checked the // user's connection in the last little while or have very // few hosts left to try. long curTime = System.currentTimeMillis(); if (!isConnected() && _connectionAttempts > 40 && ((curTime - _lastSuccessfulConnect) > 4000) && ((curTime - _lastConnectionCheck) > 60 * 60 * 1000)) { _connectionAttempts = 0; _lastConnectionCheck = curTime; LOG.debug("checking for live connection"); connectionCheckerManager.checkForLiveConnection(); } } } /** * This method notifies the connection manager that the user does not have * a live connection to the Internet to the best of our determination. * In this case, we notify the user with a message and maintain any * Gnutella hosts we have already tried instead of discarding them. */ public void noInternetConnection() { // Notify the user that they have no internet connection and that // we will automatically retry dispatchEvent(new ConnectionLifecycleEvent(ConnectionManagerImpl.this, ConnectionLifecycleEventType.NO_INTERNET)); if(_automaticallyConnecting) { // We've already notified the user about their connection and we're // alread retrying automatically, so just return. return; } // Kill all of the ConnectionFetchers. disconnect(false); // Try to reconnect periodically. backgroundExecutor.scheduleWithFixedDelay(new Runnable() { public void run() { // If the last time the user disconnected is more recent // than when we started automatically connecting, just // return without trying to connect. Note that the // disconnect time is reset if the user selects to connect. if(_automaticConnectTime < _disconnectTime) { return; } if(!connectionServices.isConnected()) { // Try to re-connect. Note this call resets the time // for our last check for a live connection, so we may // hit web servers again to check for a live connection. connect(); } } }, 10*1000, 30*1000, TimeUnit.MILLISECONDS); _automaticConnectTime = System.currentTimeMillis(); _automaticallyConnecting = true; hostCatcher.get().noInternetConnection(); } /** * Utility method to see if the passed in locale matches * that of the local client. As of now, we assume that * those clients not advertising locale as english locale */ private boolean checkLocale(String loc) { if(loc == null) loc = /** assume english if locale is not given... */ ApplicationSettings.DEFAULT_LOCALE.get(); return ApplicationSettings.LANGUAGE.get().equals(loc); } /** * Registers a listener for ConnectionLifeCycleEvents */ public void addEventListener(ConnectionLifecycleListener listener) { if (listener == null) { throw new NullPointerException("ConnectionLifecycleListener is null"); } if(!connectionLifeCycleListeners.addIfAbsent(listener)) { throw new IllegalArgumentException("Listener "+listener+" already registered"); } } /** * Dispatches a ConnectionLifecycleEvent to any registered listeners */ public void dispatchEvent(ConnectionLifecycleEvent event) { for(ConnectionLifecycleListener listener : connectionLifeCycleListeners) { listener.handleConnectionLifecycleEvent(event); } } /** * unregisters a listener for ConnectionLifeCycleEvents */ public void removeEventListener(ConnectionLifecycleListener listener) { connectionLifeCycleListeners.remove(listener); } /** * Count how many connections have already received N messages */ public int countConnectionsWithNMessages(int messageThreshold) { int count = 0; int msgs; // Count the messages on initialized connections for(RoutedConnection c : getInitializedConnections()) { msgs = c.getConnectionMessageStatistics().getNumMessagesSent(); msgs += c.getConnectionMessageStatistics().getNumMessagesReceived(); if ( msgs > messageThreshold ) count++; } return count; } /** * Count up all the messages on active connections */ public int getActiveConnectionMessages() { int count = 0; // Count the messages on initialized connections for(RoutedConnection c : getInitializedConnections()) { count += c.getConnectionMessageStatistics().getNumMessagesSent(); count += c.getConnectionMessageStatistics().getNumMessagesReceived(); } return count; } public boolean canSendConnectBack(Network network) { if (network == Network.TCP) return numTCPConnectBacksLeft > 0; if (network == Network.UDP) return numUDPConnectBacksLeft > 0; return false; } public void connectBackSent(Network network) { if (network == Network.TCP) numTCPConnectBacksLeft--; else if (network == Network.UDP) numUDPConnectBacksLeft--; else throw new IllegalArgumentException("which network?"); } /** * a class that uses the Inspection framework to provide the same functionality * as the former GetStatsVM. */ private class LegacyConnectionStats implements Inspectable { /** Whether to report only leaf connections or only up connections */ private final boolean leaf; private LegacyConnectionStats(boolean leaf) { this.leaf = leaf; } @Override public Object inspect() { if(leaf && !isSupernode()) return Collections.EMPTY_MAP; List<RoutedConnection> conns = getConnections(); Map<String,Object> ret = new HashMap<String,Object>(conns.size()); for(RoutedConnection mc : conns) { if (isSupernode()) { if (leaf && mc.getConnectionCapabilities().isSupernodeConnection()) continue; if (!leaf && mc.getConnectionCapabilities().isSupernodeClientConnection()) continue; } ret.put(mc.getAddress()+":"+mc.getPort(), ((Inspectable)mc).inspect()); } return ret; } } public void handleEvent(final GnutellaConnectionEvent event) { Set<Connectable> pushProxies = getPushProxies(); if (!pushProxies.isEmpty()) { networkManager.newPushProxies(pushProxies); } } }