package com.limegroup.gnutella.connection; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.limegroup.gnutella.MessageListener; import com.limegroup.gnutella.ReplyHandler; import com.limegroup.gnutella.RouterService; import com.limegroup.gnutella.UDPPinger; import com.limegroup.gnutella.UDPService; import com.limegroup.gnutella.io.ConnectObserver; import com.limegroup.gnutella.messages.Message; import com.limegroup.gnutella.messages.PingRequest; import com.limegroup.gnutella.util.Cancellable; import com.limegroup.gnutella.util.CommonUtils; import com.limegroup.gnutella.util.IOUtils; import com.limegroup.gnutella.util.Sockets; import com.limegroup.gnutella.util.ThreadFactory; /** * Specialized class that attempts to connect to a rotating list of well-known * Internet addresses to check whether or not this host has a live connection * to the Internet. */ public final class ConnectionChecker implements Runnable { /** * Flag for whether or not we know for sure that we're connected from * successfully connecting to an external host. */ private volatile boolean _connected; /** * Variable for the number of unsuccessful connection attempts. */ private int _unsuccessfulAttempts; /** * Whether we have tried to work around SP2 cutting us off. */ private boolean _triedSP2Workaround; /** * Log for logging this class. */ private static final Log LOG = LogFactory.getLog(ConnectionChecker.class); /** * Array of standard internet hosts to connect to when determining whether * or not the user has a live Internet connection. These are randomized * so a minimum number is hit on each check. Note that we only hit one * random server per test and that we only test the connection if we have * ample evidence that the users machine is no longer connected, resulting * in minimal traffic to these sites. NON-FINAL FOR TESTING. */ private static String[] STANDARD_HOSTS = { "www.wanadoo.fr", "www.tiscali.com", "www.ntt.com", "www.tonline.com", "www.download.com", "www.ibm.com", "www.sun.com", "www.apple.com", "www.ebay.com", "www.sun.com", "www.monster.com", "www.uunet.com", "www.real.com", "www.microsoft.com", "www.sco.com", "www.google.com", "www.cnn.com", "www.amazon.com", "www.espn.com", "www.yahoo.com", "www.oracle.com", "www.dell.com", "www.ge.com", "www.sprint.com", "www.att.com", "www.mci.com", "www.cisco.com", "www.intel.com", "www.motorola.com", "www.hp.com", "www.gateway.com", "www.sony.com", "www.ford.com", "www.gm.com", "www.aol.com", "www.verizon.com", "www.passport.com", "www.go.com", "www.overture.com", "www.earthlink.net", "www.bellsouth.net", "www.excite.com", "www.paypal.com", "www.altavista.com", "www.weather.com", "www.mapquest.com", "www.geocities.com", "www.juno.com", "www.msnbc.com", "www.lycos.com", "www.comcast.com", }; /** * Private constructor ensures that only this class can create instances of * itself. */ private ConnectionChecker() {} private static ConnectionChecker current; /** * Creates a new <tt>ConnectionChecker</tt> instance that checks for a live * internet connection. If the checker determines that there is no active * connection, it will notify the <tt>ConnectionManager</tt> to take * appropriate action. * * @return a new <tt>ConnectionChecker</tt> instance */ public static ConnectionChecker checkForLiveConnection() { LOG.trace("checking for live connection"); ConnectionChecker checker; boolean startThread = false; synchronized(ConnectionChecker.class) { if (current == null) { startThread = true; current = new ConnectionChecker(); } checker = current; } // Only create a new thread if one isn't alive. if(startThread) { LOG.debug("Starting a new connection-checker thread"); ThreadFactory.startThread(checker, "check for live connection"); } return checker; } /** * Checks for a live internet connection. */ public synchronized void run() { try { List hostList = Arrays.asList(STANDARD_HOSTS); // Add some randomization. Collections.shuffle(hostList); Iterator iter = hostList.iterator(); while(iter.hasNext()) { String curHost = (String)iter.next(); connectToHost(curHost); // Break out of the loop if we've already discovered that we're // connected -- we only need to successfully connect to one host // to know for sure that we're up. if(_connected) { LOG.debug("Connection exists."); // if we did disconnect as an attempt to work around SP2, connect now. if (_triedSP2Workaround && !RouterService.isConnected() && !RouterService.isConnecting()) { LOG.debug("Reconnecting RouterService"); RouterService.connect(); } return; } // Stop if we've failed to connect to more than 2 of the hosts // that should be up all of the time. We do this to make extra // sure the user's connection is down. If it is down, trying // multiple times adds no load to the test servers. if(_unsuccessfulAttempts > 2) { LOG.debug("Failed connection check more than twice."); if (_triedSP2Workaround || !CommonUtils.isWindowsXP()) { RouterService.getConnectionManager().noInternetConnection(); return; } else { _triedSP2Workaround = true; trySP2Workaround(); } } } } finally { synchronized(ConnectionChecker.class) { current = null; } } } private void trySP2Workaround() { if (hasNoTransfers() && udpIsDead()) return; // really disconnected else killAndSleep(); // otherwise shut off all attempts until sp2's limit times out } /** * @return true if we don't have any transfers going at non-zero speed */ private boolean hasNoTransfers(){ RouterService.getDownloadManager().measureBandwidth(); float down = RouterService.getDownloadManager().getMeasuredBandwidth(); if (down != 0) return false; RouterService.getUploadManager().measureBandwidth(); float up = RouterService.getUploadManager().getMeasuredBandwidth(); return up == 0; } /** * @return if we think that udp traffic is dead */ private boolean udpIsDead() { PingRequest ping = PingRequest.createUDPPing(); Collection hosts = RouterService.getPreferencedHosts(false,"en",50); UDPPinger myPinger = RouterService.getHostCatcher().getPinger(); UDPChecker checker = new UDPChecker(); // send some hosts to be ranked myPinger.rank(hosts,checker,checker,ping); long now = System.currentTimeMillis(); synchronized(checker) { try { // since there may be other udp packets backed up to be sent, // check every second if we have received something, and if so // cancel the hosts we sent. for (int i = 0; i < 5; i++) { checker.wait(1000); if (UDPService.instance().getLastReceivedTime() > now) { checker.received = true; return false; } } } catch (InterruptedException ignored){} } return !checker.received; } /** * Terminates all attempts to open new sockets */ private void killAndSleep() { RouterService.disconnect(); try { Thread.sleep(5*1000); } catch (InterruptedException ignored){} _unsuccessfulAttempts = 0; } /** * Determines whether or not we have connected to an external host, * verifying that we have an internet connection. * * @return <tt>true</tt> if we have created a successful connection, * otherwise <tt>false</tt> */ public boolean hasConnected() { return _connected; } /** * Connects to an individual host. * * @param host the host to connect to */ private void connectToHost(String host) { if(LOG.isDebugEnabled()) LOG.debug("Checking for connection with host: " + host); try { InetAddress.getByName(host); // die fast if unresolvable Observer observer = new Observer(); synchronized(observer) { Socket s = Sockets.connect(host, 80, 20000, observer); LOG.debug("Waiting for callback..."); try { observer.wait(40000); } catch(InterruptedException e) {} if(!observer.hasResponse()) { LOG.debug("No response!"); // only consider unsuccesful if we were able to remove it // 'cause if it couldn't be removed, a response is still pending... if(Sockets.removeConnectObserver(observer)) { LOG.debug("Removed observer"); _unsuccessfulAttempts++; IOUtils.close(s); } } } } catch (IOException bad) { LOG.debug("failed to resolve name", bad); _unsuccessfulAttempts++; } } private class Observer implements ConnectObserver { boolean response = false; // unused. public void handleIOException(IOException iox) {} // Yay, we're connected. public synchronized void handleConnect(Socket socket) throws IOException { if(!response) { LOG.debug("Socket connected OK"); response = true; _connected = true; notify(); IOUtils.close(socket); } } public synchronized void shutdown() { if(!response) { LOG.debug("Socket failed to connect"); response = true; _unsuccessfulAttempts++; notify(); } } public boolean hasResponse() { return response; } } private class UDPChecker implements MessageListener, Cancellable { volatile boolean received; public boolean isCancelled() { return received; } public void processMessage(Message m, ReplyHandler handler) { received = true; synchronized(this) { notify(); } } public void registered(byte[] guid) {} public void unregistered(byte[] guid) {} } }