package com.limegroup.gnutella.handshaking;
import java.util.Properties;
import com.limegroup.gnutella.RouterService;
import com.limegroup.gnutella.statistics.HandshakingStat;
import com.limegroup.gnutella.util.NetworkUtils;
/**
* A very simple responder to be used by ultrapeers during the
* connection handshake while accepting incoming connections
*/
public class UltrapeerHandshakeResponder extends DefaultHandshakeResponder {
/**
* Creates a new instance of ClientHandshakeResponder
* @param manager Instance of connection manager, managing this
* connection
* @param router Instance of message router, to get correct local
* address at runtime.
* @param host The host with whom we are handshaking
*/
public UltrapeerHandshakeResponder(String host) {
super(host);
}
/**
* Respond to an outgoing connection request.
*
* @param response the headers read from the connection
*/
protected HandshakeResponse respondToOutgoing(HandshakeResponse response) {
//Outgoing connection.
//If our slots are full, reject it.
if (!_manager.allowConnection(response)) {
HandshakingStat.UP_OUTGOING_REJECT_FULL.incrementStat();
return HandshakeResponse.createRejectOutgoingResponse();
}
Properties ret = new Properties();
// They might be giving us guidance
// (We don't give them guidance for outgoing)
if (response.hasLeafGuidance()) {
// Become a leaf if its a good ultrapeer & we can do it.
if (_manager.allowLeafDemotion() && response.isGoodUltrapeer()) {
HandshakingStat.UP_OUTGOING_GUIDANCE_FOLLOWED.incrementStat();
ret.put(HeaderNames.X_ULTRAPEER, "False");
} else { //Had guidance, but we aren't going to be a leaf.
HandshakingStat.UP_OUTGOING_GUIDANCE_IGNORED.incrementStat();
//fall through to accept, we're ignoring the guidance.
}
} else
HandshakingStat.UP_OUTGOING_ACCEPT.incrementStat();
// deflate if we can ...
if (response.isDeflateAccepted()) {
ret.put(HeaderNames.CONTENT_ENCODING, HeaderNames.DEFLATE_VALUE);
}
// accept the response
return HandshakeResponse.createAcceptOutgoingResponse(ret);
}
/**
* Respond to an incoming connection request.
*
* @param response the headers read from the connection
*/
protected HandshakeResponse respondToIncoming(HandshakeResponse response) {
// if this is a connections from the crawler, return the special crawler
// response
if (response.isCrawler()) {
HandshakingStat.INCOMING_CRAWLER.incrementStat();
return HandshakeResponse.createCrawlerResponse();
}
//Incoming connection....
Properties ret = new UltrapeerHeaders(getRemoteIP());
//give own IP address
ret.put(HeaderNames.LISTEN_IP,
NetworkUtils.ip2string(RouterService.getAddress())+":"
+ RouterService.getPort());
//Decide whether to allow or reject. Somewhat complicated because
//of ultrapeer guidance.
if (reject(response, ret)) {
// reject the connection, and let the other node know about
// any Ultrapeers we're connected to
return HandshakeResponse.createUltrapeerRejectIncomingResponse(response);
}
//We do this last, to prevent reject connections from being deflated,
//which may actually increase the amount of bandwidth needed.
if (response.isDeflateAccepted()) {
ret.put(HeaderNames.CONTENT_ENCODING, HeaderNames.DEFLATE_VALUE);
}
// accept the connection, and let the connecting node know about
// Ultrapeers that are as many hops away as possible, to avoid
// cycles.
return HandshakeResponse.createAcceptIncomingResponse(response, ret);
}
/**
* Returns true if this incoming connections should be rejected with a 503.
*/
private boolean reject(HandshakeResponse response, Properties ret) {
// See if this connection can be allowed as a leaf.
boolean allowedAsLeaf = _manager.allowConnectionAsLeaf(response);
// If the user wasn't an ultrapeer, accept or reject
// based on whether or not it was allowed.
// This is because leaf connections cannot upgrade to ultrapeers,
// so the allowAsLeaf was the final check.
if (response.isLeaf() ) {
if (!allowedAsLeaf)
HandshakingStat.UP_INCOMING_REJECT_LEAF.incrementStat();
else
HandshakingStat.UP_INCOMING_ACCEPT_LEAF.incrementStat();
return !allowedAsLeaf;
}
// Otherwise (if the user is an ultrapeer), there are a few things...
boolean supernodeNeeded = _manager.supernodeNeeded();
// If we can accept them and we don't need more supernodes,
// guide them to become a leaf
if (allowedAsLeaf && !supernodeNeeded) {
HandshakingStat.UP_INCOMING_GUIDED.incrementStat();
ret.put(HeaderNames.X_ULTRAPEER_NEEDED, Boolean.FALSE.toString());
return false;
}
boolean allowedAsUltrapeer = _manager.allowConnection(response);
// If supernode is needed or we can't accept them as a leaf,
// see if we can accept them as a supernode.
if (allowedAsUltrapeer) {
HandshakingStat.UP_INCOMING_ACCEPT_UP.incrementStat();
// not strictly necessary ...
ret.put(HeaderNames.X_ULTRAPEER_NEEDED, Boolean.TRUE.toString());
return false;
}
// In all other cases, we must reject the connection.
// These are:
// 1) !allowedAsLeaf && !allowedAsUltrapeer
// 2) supernodeNeeded && !alloweedAsUltrapeer
// The reasoning behind 1) is that we cannot accept them as a either a
// leaf or an ultrapeer, so we must reject.
// The reasoning behind 2) is that the network needs a supernode, but
// we are currently unable to service that need, so we must reject.
// Theoretically, it is possible to allow them as a leaf even if
// a supernode was needed, but that would lower the amount of
// well-connected supernodes, ultimately hurting the network.
// This means that the last 10% of leaf slots will always be reserved
// for connections that are unable to be ultrapeers.
if (!allowedAsLeaf)
HandshakingStat.UP_INCOMING_REJECT_NO_ROOM_LEAF.incrementStat();
else
HandshakingStat.UP_INCOMING_REJECT_NO_ROOM_UP.incrementStat();
return true;
}
}