package com.limegroup.gnutella.connection;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.limewire.io.IOUtils;
import org.limewire.net.SocketsManager;
import org.limewire.nio.observer.ConnectObserver;
import org.limewire.util.OSUtils;
import com.limegroup.gnutella.ConnectionServices;
import com.limegroup.gnutella.DownloadServices;
import com.limegroup.gnutella.UploadServices;
/**
* 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 class ConnectionChecker {
private static final Log LOG = LogFactory.getLog(ConnectionChecker.class);
/**
* 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;
private final ConnectionServices connectionServices;
private final UploadServices uploadServices;
private final SocketsManager socketsManager;
private final AtomicInteger numWorkarounds;
private final UDPConnectionChecker udpConnectionChecker;
private final DownloadServices downloadServices;
private final List<String> hostList;
public ConnectionChecker(AtomicInteger numWorkarounds, String[] hosts,
ConnectionServices connectionServices, UploadServices uploadServices,
DownloadServices downloadServices, SocketsManager socketsManager,
UDPConnectionChecker udpConnectionChecker) {
this.numWorkarounds = numWorkarounds;
this.hostList = new ArrayList<String>(Arrays.asList(hosts));
this.connectionServices = connectionServices;
this.uploadServices = uploadServices;
this.downloadServices = downloadServices;
this.socketsManager = socketsManager;
this.udpConnectionChecker = udpConnectionChecker;
}
/**
* Checks for a live Internet connection.
*/
public synchronized void run(ConnectionCheckerListener connectionCheckerListener) {
// Add some randomization.
Collections.shuffle(hostList);
for (String curHost : hostList) {
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 && !connectionServices.isConnected()
&& !connectionServices.isConnecting()) {
LOG.debug("Reconnecting RouterService");
connectionServices.connect();
}
connectionCheckerListener.connected();
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 || !shouldTrySP2Workaround()) {
break;
} else if (hasNoTransfers() && udpConnectionChecker.udpIsDead()) {
// really disconnected
break;
} else {
// otherwise shut off all attempts until sp2's limit times
// out
triedSP2Workaround = true;
trySP2Workaround();
}
}
}
connectionCheckerListener.noInternetConnection();
}
/**
* For testing.
*/
boolean shouldTrySP2Workaround() {
return OSUtils.isSocketChallengedWindows();
}
/**
* Terminates all attempts to open new sockets.
*/
void trySP2Workaround() {
connectionServices.disconnect();
numWorkarounds.incrementAndGet();
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException ignored) {
}
unsuccessfulAttempts = 0;
}
/**
* @return true if we don't have any transfers going at non-zero speed
*/
private boolean hasNoTransfers() {
return !downloadServices.hasActiveDownloads() && !uploadServices.hasActiveUploads();
}
/**
* 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 = socketsManager.connect(new InetSocketAddress(host, 80), 6000, observer);
LOG.debug("Waiting for callback...");
try {
observer.wait(12000);
} catch (InterruptedException e) {
}
if (!observer.hasResponse()) {
LOG.debug("No response!");
// only consider unsuccessful if we were able to remove it
// 'cause if it couldn't be removed, a response is still
// pending...
if (socketsManager.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;
}
}
}