/* * File : PEPeerTransportProtocol.java * Created : 22-Oct-2003 * By : stuff * * Azureus - a Java Bittorrent client * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details ( see the LICENSE file ). * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.gudy.azureus2.core3.peer.impl.transport; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.security.PublicKey; import java.security.SecureRandom; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.config.ParameterListener; import org.gudy.azureus2.core3.disk.DiskManager; import org.gudy.azureus2.core3.disk.DiskManagerPiece; import org.gudy.azureus2.core3.disk.DiskManagerReadRequest; import org.gudy.azureus2.core3.logging.LogAlert; import org.gudy.azureus2.core3.logging.LogEvent; import org.gudy.azureus2.core3.logging.LogIDs; import org.gudy.azureus2.core3.logging.LogRelation; import org.gudy.azureus2.core3.logging.Logger; import org.gudy.azureus2.core3.peer.PEPeer; import org.gudy.azureus2.core3.peer.PEPeerListener; import org.gudy.azureus2.core3.peer.PEPeerManager; import org.gudy.azureus2.core3.peer.PEPeerSource; import org.gudy.azureus2.core3.peer.PEPeerStats; import org.gudy.azureus2.core3.peer.impl.PEPeerControl; import org.gudy.azureus2.core3.peer.impl.PEPeerTransport; import org.gudy.azureus2.core3.peer.impl.PEPeerTransportFactory; import org.gudy.azureus2.core3.peer.util.PeerIdentityDataID; import org.gudy.azureus2.core3.peer.util.PeerIdentityManager; import org.gudy.azureus2.core3.peer.util.PeerUtils; import org.gudy.azureus2.core3.util.AEMonitor; import org.gudy.azureus2.core3.util.AddressUtils; import org.gudy.azureus2.core3.util.Constants; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.DirectByteBuffer; import org.gudy.azureus2.core3.util.DirectByteBufferPool; import org.gudy.azureus2.core3.util.HashWrapper; import org.gudy.azureus2.core3.util.IPToHostNameResolver; import org.gudy.azureus2.core3.util.IPToHostNameResolverListener; import org.gudy.azureus2.core3.util.IPToHostNameResolverRequest; import org.gudy.azureus2.core3.util.IndentWriter; import org.gudy.azureus2.core3.util.SHA1Hasher; import org.gudy.azureus2.core3.util.SimpleTimer; import org.gudy.azureus2.core3.util.StringInterner; import org.gudy.azureus2.core3.util.SystemTime; import org.gudy.azureus2.core3.util.TimerEvent; import org.gudy.azureus2.core3.util.TimerEventPerformer; import org.gudy.azureus2.plugins.dht.mainline.MainlineDHTProvider; import org.gudy.azureus2.plugins.network.Connection; import org.gudy.azureus2.pluginsimpl.local.network.ConnectionImpl; import com.aelitis.azureus.core.impl.AzureusCoreImpl; import com.aelitis.azureus.core.networkmanager.ConnectionEndpoint; import com.aelitis.azureus.core.networkmanager.IncomingMessageQueue; import com.aelitis.azureus.core.networkmanager.LimitedRateGroup; import com.aelitis.azureus.core.networkmanager.NetworkConnection; import com.aelitis.azureus.core.networkmanager.NetworkManager; import com.aelitis.azureus.core.networkmanager.OutgoingMessageQueue; import com.aelitis.azureus.core.networkmanager.ProtocolEndpoint; import com.aelitis.azureus.core.networkmanager.Transport; import com.aelitis.azureus.core.networkmanager.impl.NetworkConnectionImpl; import com.aelitis.azureus.core.networkmanager.impl.tcp.ProtocolEndpointTCP; import com.aelitis.azureus.core.networkmanager.impl.tcp.TCPNetworkManager; import com.aelitis.azureus.core.networkmanager.impl.udp.ProtocolEndpointUDP; import com.aelitis.azureus.core.networkmanager.impl.udp.UDPNetworkManager; import com.aelitis.azureus.core.peermanager.messaging.Message; import com.aelitis.azureus.core.peermanager.messaging.MessageManager; import com.aelitis.azureus.core.peermanager.messaging.azureus.AZBadPiece; import com.aelitis.azureus.core.peermanager.messaging.azureus.AZHandshake; import com.aelitis.azureus.core.peermanager.messaging.azureus.AZHave; import com.aelitis.azureus.core.peermanager.messaging.azureus.AZMessage; import com.aelitis.azureus.core.peermanager.messaging.azureus.AZMessageDecoder; import com.aelitis.azureus.core.peermanager.messaging.azureus.AZMessageEncoder; import com.aelitis.azureus.core.peermanager.messaging.azureus.AZPeerExchange; import com.aelitis.azureus.core.peermanager.messaging.azureus.AZRequestHint; import com.aelitis.azureus.core.peermanager.messaging.azureus.AZStylePeerExchange; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTBitfield; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTCancel; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTChoke; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTDHTPort; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTHandshake; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTHave; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTInterested; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTKeepAlive; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTMessage; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTMessageDecoder; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTMessageEncoder; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTMessageFactory; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTPiece; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTRawMessage; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTRequest; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTUnchoke; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTUninterested; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.ltep.LTHandshake; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.ltep.LTMessage; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.ltep.LTMessageDecoder; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.ltep.LTMessageEncoder; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.ltep.UTPeerExchange; import com.aelitis.azureus.core.peermanager.peerdb.PeerExchangerItem; import com.aelitis.azureus.core.peermanager.peerdb.PeerItem; import com.aelitis.azureus.core.peermanager.peerdb.PeerItemFactory; import com.aelitis.azureus.core.peermanager.piecepicker.PiecePicker; import com.aelitis.azureus.core.peermanager.piecepicker.impl.PiecePickerImpl; import com.aelitis.azureus.core.peermanager.piecepicker.util.BitFlags; import com.aelitis.azureus.core.peermanager.utils.AZPeerIdentityManager; import com.aelitis.azureus.core.peermanager.utils.ClientIdentifier; import com.aelitis.azureus.core.peermanager.utils.OutgoingBTHaveMessageAggregator; import com.aelitis.azureus.core.peermanager.utils.OutgoingBTPieceMessageHandler; import com.aelitis.azureus.core.peermanager.utils.OutgoingBTPieceMessageHandlerAdapter; import com.aelitis.azureus.core.peermanager.utils.PeerClassifier; import com.aelitis.azureus.core.peermanager.utils.PeerMessageLimiter; import edu.uw.cse.netlab.reputation.Computation; import edu.uw.cse.netlab.reputation.LocalIdentity; import edu.uw.cse.netlab.reputation.ReceiptDispatcher; import edu.uw.cse.netlab.reputation.messages.Attestation; import edu.uw.cse.netlab.reputation.messages.CertificateExchange; import edu.uw.cse.netlab.reputation.messages.ReceiptBundle; import edu.uw.cse.netlab.reputation.messages.ReceiptRequests; import edu.uw.cse.netlab.reputation.storage.LocalTopK; import edu.uw.cse.netlab.reputation.storage.Receipt; import edu.uw.cse.netlab.reputation.storage.ReputationDAO; import edu.uw.cse.netlab.utils.BloomFilter; import edu.uw.cse.netlab.utils.KeyManipulation; public class PEPeerTransportProtocol extends LogRelation implements PEPeerTransport { protected final static LogIDs LOGID = LogIDs.PEER; private volatile int _lastPiece = -1; // last piece that was requested // from this peer (mostly to try to // request from same one) private final HashMap<Long, Receipt> requested_receipts = new HashMap<Long, Receipt>(); public Receipt[] getSharedIntermediaries() { return requested_receipts.values().toArray(new Receipt[0]); } protected final PEPeerControl manager; protected final DiskManager diskManager; protected final PiecePicker piecePicker; protected final int nbPieces; private final String peer_source; private byte[] peer_id; private final String ip; protected String ip_resolved; private IPToHostNameResolverRequest ip_resolver_request; private int port; private PeerItem peer_item_identity; private int tcp_listen_port = 0; private int udp_listen_port = 0; private int udp_non_data_port = 0; private byte crypto_level; protected PEPeerStats peer_stats; private final ArrayList requested = new ArrayList(); private final AEMonitor requested_mon = new AEMonitor("PEPeerTransportProtocol:Req"); private Map data; private long lastNeededUndonePieceChange; protected boolean choked_by_other_peer = true; /** total time the other peer has unchoked us while not snubbed */ protected long unchokedTimeTotal; /** the time at which the other peer last unchoked us when not snubbed */ protected long unchokedTime; protected boolean choking_other_peer = true; private boolean interested_in_other_peer = false; private boolean other_peer_interested_in_me = false; private long snubbed = 0; /** lazy allocation; null until needed */ private volatile BitFlags peerHavePieces = null; private volatile boolean availabilityAdded = false; private volatile boolean received_bitfield; private boolean handshake_sent; private boolean seed_set_by_accessor = false; private final boolean incoming; protected volatile boolean closing = false; private volatile int current_peer_state; protected NetworkConnection connection; public double getWeight() { return ((NetworkConnectionImpl) connection).getWeight(); } public void setWeight(double inWeight) { ((NetworkConnectionImpl) connection).setWeight(inWeight); } double rep = 0; public double getReputation() { return rep; } public void setReputation( double rep ) { this.rep = rep; } private OutgoingBTPieceMessageHandler outgoing_piece_message_handler; private OutgoingBTHaveMessageAggregator outgoing_have_message_aggregator; private Connection plugin_connection; private boolean identityAdded = false; // needed so we don't remove id's in // closeAll() on duplicate // connection attempts protected int connection_state = PEPeerTransport.CONNECTION_PENDING; private String client = ""; // Client name to show to user. private String client_peer_id = ""; // Client name derived from the peer ID. private String client_handshake = ""; // Client name derived from the // handshake. private String client_handshake_version = ""; // Client version derived // from the handshake. // When superSeeding, number of unique piece announced private int uniquePiece = -1; // When downloading a piece in exclusivity mode the piece number being // downloaded private int reservedPiece = -1; // Spread time (0 secs , fake default) private int spreadTimeHint = 0 * 1000; protected long last_message_sent_time = 0; protected long last_message_received_time = 0; protected long last_data_message_received_time = -1; protected long last_good_data_time = -1; // time data written to disk was // recieved protected long last_data_message_sent_time = -1; private long connection_established_time = 0; private int consecutive_no_request_count; private int messaging_mode = MESSAGING_BT_ONLY; private Message[] supported_messages = null; private byte other_peer_bitfield_version = BTMessageFactory.MESSAGE_VERSION_INITIAL; private byte other_peer_cancel_version = BTMessageFactory.MESSAGE_VERSION_INITIAL; private byte other_peer_choke_version = BTMessageFactory.MESSAGE_VERSION_INITIAL; private byte other_peer_handshake_version = BTMessageFactory.MESSAGE_VERSION_INITIAL; private byte other_peer_bt_have_version = BTMessageFactory.MESSAGE_VERSION_INITIAL; private byte other_peer_az_have_version = BTMessageFactory.MESSAGE_VERSION_INITIAL; private byte other_peer_interested_version = BTMessageFactory.MESSAGE_VERSION_INITIAL; private byte other_peer_keep_alive_version = BTMessageFactory.MESSAGE_VERSION_INITIAL; private byte other_peer_pex_version = BTMessageFactory.MESSAGE_VERSION_INITIAL; private byte other_peer_piece_version = BTMessageFactory.MESSAGE_VERSION_INITIAL; private byte other_peer_unchoke_version = BTMessageFactory.MESSAGE_VERSION_INITIAL; private byte other_peer_uninterested_version = BTMessageFactory.MESSAGE_VERSION_INITIAL; private byte other_peer_request_version = BTMessageFactory.MESSAGE_VERSION_INITIAL; private final byte other_peer_bt_lt_ext_version = BTMessageFactory.MESSAGE_VERSION_INITIAL; private byte other_peer_az_request_hint_version = BTMessageFactory.MESSAGE_VERSION_INITIAL; private byte other_peer_az_bad_piece_version = BTMessageFactory.MESSAGE_VERSION_INITIAL; // OneSwarm messages private byte os_certificate_exchange = BTMessageFactory.MESSAGE_VERSION_INITIAL; private byte os_receipt_requests = BTMessageFactory.MESSAGE_VERSION_INITIAL; private byte os_receipt_bundle = BTMessageFactory.MESSAGE_VERSION_INITIAL; private byte os_attestation = BTMessageFactory.MESSAGE_VERSION_INITIAL; private boolean ut_pex_enabled = false; private boolean ml_dht_enabled = false; private final AEMonitor closing_mon = new AEMonitor("PEPeerTransportProtocol:closing"); private final AEMonitor general_mon = new AEMonitor("PEPeerTransportProtocol:data"); private byte[] handshake_reserved_bytes = null; private LinkedHashMap recent_outgoing_requests; private AEMonitor recent_outgoing_requests_mon; private boolean has_received_initial_pex = false; private static final boolean SHOW_DISCARD_RATE_STATS; static { final String prop = System.getProperty("show.discard.rate.stats"); SHOW_DISCARD_RATE_STATS = prop != null && prop.equals("1"); } private static int requests_discarded = 0; private static int requests_discarded_endgame = 0; private static int requests_recovered = 0; private static int requests_completed = 0; private static final int REQUEST_HINT_MAX_LIFE = PiecePicker.REQUEST_HINT_MAX_LIFE + 30 * 1000; private int[] request_hint; private List peer_listeners_cow; private final AEMonitor peer_listeners_mon = new AEMonitor("PEPeerTransportProtocol:PL"); private ReceiptDispatcher mReceiptDispatcher = null; // this gets created // if this is a // OneSwarm // connection private Set<PublicKey> mAttribution = null; // the reported attribution of a // remote peer (if one was // received) public Set<PublicKey> getAttribution() { return mAttribution; } // certain Optimum Online networks block peer seeding via "complete" // bitfield message filtering // lazy mode makes sure we never send a complete (seed) bitfield protected static boolean ENABLE_LAZY_BITFIELD; private static final Random rnd = new SecureRandom(); private static final class DisconnectedTransportQueue extends LinkedHashMap { public DisconnectedTransportQueue() { super(20, 0.75F); } private static final long MAX_CACHE_AGE = 2 * 60 * 1000; // remove all elements older than 2 minutes until we hit the 20 again private void performCleaning() { if (size() > 20) { Iterator it = values().iterator(); long now = SystemTime.getCurrentTime(); while (it.hasNext() && size() > 20) { QueueEntry eldest = (QueueEntry) it.next(); if (now < eldest.addTime || now - eldest.addTime > MAX_CACHE_AGE) { it.remove(); } else { break; } } } } private static final class QueueEntry { public QueueEntry(PEPeerTransportProtocol trans) { transport = trans; } final PEPeerTransportProtocol transport; final long addTime = SystemTime.getCurrentTime(); } // hardcap at 100 @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > 100; } synchronized public Object put(HashWrapper key, PEPeerTransportProtocol value) { performCleaning(); return super.put(key, new QueueEntry(value)); } synchronized public PEPeerTransportProtocol remove(HashWrapper key) { performCleaning(); QueueEntry entry = (QueueEntry) super.remove(key); if (entry != null) return entry.transport; else return null; } } private static final DisconnectedTransportQueue recentlyDisconnected = new DisconnectedTransportQueue(); private static boolean fast_unchoke_new_peers; static { rnd.setSeed(SystemTime.getCurrentTime()); COConfigurationManager.addAndFireParameterListeners(new String[] { "Use Lazy Bitfield", "Peer.Fast.Initial.Unchoke.Enabled" }, new ParameterListener() { @Override public final void parameterChanged(String ignore) { final String prop = System.getProperty("azureus.lazy.bitfield"); ENABLE_LAZY_BITFIELD = prop != null && prop.equals("1"); ENABLE_LAZY_BITFIELD |= COConfigurationManager.getBooleanParameter("Use Lazy Bitfield"); fast_unchoke_new_peers = COConfigurationManager.getBooleanParameter("Peer.Fast.Initial.Unchoke.Enabled"); } }); } // reconnect stuff private HashWrapper peerSessionID; private HashWrapper mySessionID; { byte[] newSession = new byte[20]; rnd.nextBytes(newSession); mySessionID = new HashWrapper(newSession); } // allow reconnect if we've sent or recieved at least 1 piece over the // current connection private boolean allowReconnect; private boolean is_optimistic_unchoke = false; private PeerExchangerItem peer_exchange_item = null; private boolean peer_exchange_supported = false; protected PeerMessageLimiter message_limiter; private boolean request_hint_supported; private boolean bad_piece_supported; private boolean have_aggregation_disabled; private boolean mIsOneSwarm = false; private PublicKey[] mDirectAdvertisements = null; public PublicKey[] getDirectAdvertisements() { return mDirectAdvertisements; } // Methods call this and then use oneswarm specific information that isn't // available unless the handshake is complete public boolean isOneSwarm() { return mIsOneSwarm && this.getPeerState() == PEPeer.TRANSFERING; } private X509Certificate mCertificate = null; private AZHandshake mHandshake; public X509Certificate getCertificate() { return mCertificate; } // INCOMING public PEPeerTransportProtocol(PEPeerControl _manager, String _peer_source, NetworkConnection _connection, Map _initial_user_data) { manager = _manager; peer_source = _peer_source; connection = _connection; data = _initial_user_data; incoming = true; diskManager = manager.getDiskManager(); piecePicker = manager.getPiecePicker(); nbPieces = diskManager.getNbPieces(); InetSocketAddress notional_address = _connection.getEndpoint().getNotionalAddress(); ip = notional_address.getAddress().getHostAddress(); port = notional_address.getPort(); peer_item_identity = PeerItemFactory.createPeerItem(ip, port, PeerItem.convertSourceID(_peer_source), PeerItemFactory.HANDSHAKE_TYPE_PLAIN, 0, PeerItemFactory.CRYPTO_LEVEL_1, 0); // this // will // be // recreated // upon // az // handshake // decode plugin_connection = new ConnectionImpl(connection); peer_stats = manager.createPeerStats(this); changePeerState(PEPeer.CONNECTING); } @Override public void start() { // split out connection initiation from constructor so we can get access // to the peer transport // before message processing starts if (incoming) { // "fake" a connect request to register our listener connection.connect(false, new NetworkConnection.ConnectionListener() { @Override public final void connectStarted() { connection_state = PEPeerTransport.CONNECTION_CONNECTING; } @Override public final void connectSuccess(ByteBuffer remaining_initial_data) { // will // be // called // immediately if (Logger.isEnabled()) Logger.log(new LogEvent(PEPeerTransportProtocol.this, LOGID, "In: Established incoming connection")); initializeConnection(); /* * Waiting until we've received the initiating-end's full * handshake, before sending back our own, really should be * the "proper" behavior. However, classic BT trackers * running NAT checking will only send the first 48 bytes * (up to infohash) of the peer handshake, skipping peerid, * which means we'll never get their complete handshake, and * thus never reply, which causes the NAT check to fail. So, * we need to send our handshake earlier, after we've * verified the infohash. NOTE: This code makes the * assumption that the inbound infohash has already been * validated, as we don't check their handshake fully before * sending our own. */ sendBTHandshake(); } @Override public final void connectFailure(Throwable failure_msg) { // should // never // happen Debug.out("ERROR: incoming connect failure: ", failure_msg); closeConnectionInternally("ERROR: incoming connect failure [" + PEPeerTransportProtocol.this + "] : " + failure_msg.getMessage(), true, true); } @Override public final void exceptionThrown(Throwable error) { if (error.getMessage() == null) { Debug.out(error); } closeConnectionInternally("connection exception: " + error.getMessage(), false, true); } @Override public String getDescription() { return (getString()); } }); } else { // not pulled out startup from outbound connections yet... } } // OUTGOING public PEPeerTransportProtocol(PEPeerControl _manager, String _peer_source, String _ip, int _tcp_port, int _udp_port, boolean _use_tcp, boolean _require_crypto_handshake, byte _crypto_level, Map _initial_user_data) { manager = _manager; diskManager = manager.getDiskManager(); piecePicker = manager.getPiecePicker(); nbPieces = diskManager.getNbPieces(); lastNeededUndonePieceChange = Long.MIN_VALUE; peer_source = _peer_source; ip = _ip; port = _tcp_port; tcp_listen_port = _tcp_port; udp_listen_port = _udp_port; crypto_level = _crypto_level; data = _initial_user_data; udp_non_data_port = UDPNetworkManager.getSingleton().getUDPNonDataListeningPortNumber(); peer_item_identity = PeerItemFactory.createPeerItem(ip, tcp_listen_port, PeerItem.convertSourceID(_peer_source), PeerItemFactory.HANDSHAKE_TYPE_PLAIN, _udp_port, crypto_level, 0); // this // will // be // recreated // upon // az // handshake // decode incoming = false; peer_stats = manager.createPeerStats(this); if (port < 0 || port > 65535) { closeConnectionInternally("given remote port is invalid: " + port); return; } // either peer specific or global pref plus optional per-download level boolean use_crypto = _require_crypto_handshake || NetworkManager.getCryptoRequired(manager.getAdapter().getCryptoLevel()); if (isLANLocal()) use_crypto = false; // dont bother with PHE for lan peers InetSocketAddress endpoint_address; ProtocolEndpoint pe; if (_use_tcp) { endpoint_address = new InetSocketAddress(ip, tcp_listen_port); pe = new ProtocolEndpointTCP(endpoint_address); } else { endpoint_address = new InetSocketAddress(ip, udp_listen_port); pe = new ProtocolEndpointUDP(endpoint_address); } ConnectionEndpoint connection_endpoint = new ConnectionEndpoint(endpoint_address); connection_endpoint.addProtocol(pe); connection = NetworkManager.getSingleton().createConnection(connection_endpoint, new BTMessageEncoder(), new BTMessageDecoder(), use_crypto, !_require_crypto_handshake, manager.getSecrets(_crypto_level)); plugin_connection = new ConnectionImpl(connection); changePeerState(PEPeer.CONNECTING); ByteBuffer initial_outbound_data = null; if (use_crypto) { DirectByteBuffer[] ddbs = new BTHandshake(manager.getHash(), manager.getPeerId(), manager.isExtendedMessagingEnabled(), other_peer_handshake_version).getRawData(); int handshake_len = 0; for (int i = 0; i < ddbs.length; i++) { handshake_len += ddbs[i].remaining(DirectByteBuffer.SS_PEER); } initial_outbound_data = ByteBuffer.allocate(handshake_len); for (int i = 0; i < ddbs.length; i++) { DirectByteBuffer ddb = ddbs[i]; initial_outbound_data.put(ddb.getBuffer(DirectByteBuffer.SS_PEER)); ddb.returnToPool(); } initial_outbound_data.flip(); handshake_sent = true; } connection.connect(initial_outbound_data, !manager.isSeeding(), new NetworkConnection.ConnectionListener() { private boolean connect_ok; @Override public final void connectStarted() { connection_state = PEPeerTransport.CONNECTION_CONNECTING; } @Override public final void connectSuccess(ByteBuffer remaining_initial_data) { connect_ok = true; if (closing) { // Debug.out( "PEPeerTransportProtocol::connectSuccess() // called when closing." ); return; } if (Logger.isEnabled()) Logger.log(new LogEvent(PEPeerTransportProtocol.this, LOGID, "Out: Established outgoing connection")); initializeConnection(); if (remaining_initial_data != null && remaining_initial_data.remaining() > 0) { // queue as a *raw* message as already encoded connection.getOutgoingMessageQueue().addMessage(new BTRawMessage(new DirectByteBuffer(remaining_initial_data)), false); } sendBTHandshake(); } @Override public final void connectFailure(Throwable failure_msg) { closeConnectionInternally("failed to establish outgoing connection: " + failure_msg.getMessage(), true, true); } @Override public final void exceptionThrown(Throwable error) { if (error.getMessage() == null) { Debug.out("error.getMessage() == null", error); } closeConnectionInternally("connection exception: " + error.getMessage(), !connect_ok, true); } @Override public String getDescription() { return (getString()); } }); if (Logger.isEnabled()) Logger.log(new LogEvent(this, LOGID, "Out: Creating outgoing connection")); } protected void initializeConnection() { if (closing) return; recent_outgoing_requests = new LinkedHashMap(PiecePickerImpl.REQUESTS_MAX, .75F, true) { @Override public final boolean removeEldestEntry(Map.Entry eldest) { return size() > PiecePickerImpl.REQUESTS_MAX; } }; recent_outgoing_requests_mon = new AEMonitor("PEPeerTransportProtocol:ROR"); message_limiter = new PeerMessageLimiter(); /* * //link in outgoing piece handler outgoing_piece_message_handler = new * OutgoingBTPieceMessageHandler( this, * connection.getOutgoingMessageQueue(), new * OutgoingBTPieceMessageHandlerAdapter() { public void * diskRequestCompleted( long bytes) { peer_stats.diskReadComplete( * bytes ); } }, other_peer_piece_version); */ // link in outgoing have message aggregator outgoing_have_message_aggregator = new OutgoingBTHaveMessageAggregator(connection.getOutgoingMessageQueue(), other_peer_bt_have_version, other_peer_az_have_version); connection_established_time = SystemTime.getCurrentTime(); connection_state = PEPeerTransport.CONNECTION_WAITING_FOR_HANDSHAKE; changePeerState(PEPeer.HANDSHAKING); registerForMessageHandling(); } @Override public String getPeerSource() { return (peer_source); } /** * Close the peer connection from within the PEPeerTransport object. * * @param reason */ protected void closeConnectionInternally(String reason, boolean connect_failed, boolean network_failure) { performClose(reason, connect_failed, false, network_failure); } protected void closeConnectionInternally(String reason) { performClose(reason, false, false, false); } /** * Close the peer connection from the PEPeerControl manager side. NOTE: This * method assumes PEPeerControl already knows about the close. This method * is inteded to be only invoked by select administrative methods. You * probably should not invoke this directly. */ @Override public void closeConnection(String reason) { performClose(reason, false, true, false); } private void performClose(String reason, boolean connect_failed, boolean externally_closed, boolean network_failure) { try { closing_mon.enter(); if (closing) return; closing = true; if (mReceiptDispatcher != null) mReceiptDispatcher.check(true); // immediatly lose interest in peer interested_in_other_peer = false; lastNeededUndonePieceChange = Long.MAX_VALUE; if (isSnubbed()) manager.decNbPeersSnubbed(); if (identityAdded) { // remove identity if (peer_id != null) PeerIdentityManager.removeIdentity(manager.getPeerIdentityDataID(), peer_id, getPort()); else Debug.out("PeerIdentity added but peer_id == null !!!"); identityAdded = false; } changePeerState(PEPeer.CLOSING); } finally { closing_mon.exit(); } // cancel any pending requests (on the manager side) cancelRequests(); if (outgoing_have_message_aggregator != null) { outgoing_have_message_aggregator.destroy(); } if (peer_exchange_item != null) { peer_exchange_item.destroy(); } if (outgoing_piece_message_handler != null) { outgoing_piece_message_handler.destroy(); } if (connection != null) { // can be null if close is called within // ::<init>::, like when the given port is // invalid connection.close(); } if (ip_resolver_request != null) { ip_resolver_request.cancel(); } removeAvailability(); changePeerState(PEPeer.DISCONNECTED); if (Logger.isEnabled()) Logger.log(new LogEvent(this, LOGID, "Peer connection closed: " + reason)); if (!externally_closed) { // if closed internally, notify manager, // otherwise we assume it already knows manager.peerConnectionClosed(this, connect_failed, network_failure); } /* * all managed references should have been removed by now add to * recently disconnected list and null some stuff to make the object * lighter */ outgoing_have_message_aggregator = null; peer_exchange_item = null; outgoing_piece_message_handler = null; plugin_connection = null; // Edit: by isdal: don't remember f2f peers, there is no way to reconnect anyway if(!PEPeerSource.PS_OSF2F.equals(getPeerSource())){ // only save stats if it's worth doing so; ignore rapid // connect-disconnects if (peer_stats.getTotalDataBytesReceived() > 0 || peer_stats.getTotalDataBytesSent() > 0 || SystemTime.getCurrentTime() - connection_established_time > 30 * 1000) recentlyDisconnected.put(mySessionID, this); } } @Override public PEPeerTransport reconnect(boolean tryUDP) { boolean use_tcp = isTCP() && !(tryUDP && getUDPListenPort() > 0); if ((use_tcp && getTCPListenPort() > 0) || (!use_tcp && getUDPListenPort() > 0)) { boolean use_crypto = getPeerItemIdentity().getHandshakeType() == PeerItemFactory.HANDSHAKE_TYPE_CRYPTO; PEPeerTransport new_conn = PEPeerTransportFactory.createTransport(manager, getPeerSource(), getIp(), getTCPListenPort(), getUDPListenPort(), use_tcp, use_crypto, crypto_level, null); // log to both relations Logger.log(new LogEvent(new Object[] { this, new_conn }, LOGID, "attempting to reconnect, creating new connection")); if (new_conn instanceof PEPeerTransportProtocol) { PEPeerTransportProtocol pt = (PEPeerTransportProtocol) new_conn; pt.checkForReconnect(mySessionID); } manager.addPeer(new_conn); return (new_conn); } else { return (null); } } /* * (non-Javadoc) * * @see org.gudy.azureus2.core3.peer.impl.PEPeerTransport#isSafeForReconnect() */ @Override public boolean isSafeForReconnect() { return allowReconnect; } private void checkForReconnect(HashWrapper oldID) { PEPeerTransportProtocol oldTransport = recentlyDisconnected.remove(oldID); if (oldTransport != null) { Logger.log(new LogEvent(this, LOGID, LogAlert.AT_INFORMATION, "reassociating stats from " + oldTransport + " with this connection")); peerSessionID = oldTransport.peerSessionID; peer_stats = oldTransport.peer_stats; peer_stats.setPeer(this); unchokedTimeTotal += oldTransport.unchokedTimeTotal; unchokedTime += oldTransport.unchokedTime; setSnubbed(oldTransport.isSnubbed()); snubbed = oldTransport.snubbed; last_good_data_time = oldTransport.last_good_data_time; } } private void generateFallbackSessionId() { SHA1Hasher sha1 = new SHA1Hasher(); sha1.update(peer_id); sha1.update(getIp().getBytes()); mySessionID = sha1.getHash(); checkForReconnect(mySessionID); } private void addAvailability() { if (!availabilityAdded && !closing && peerHavePieces != null && current_peer_state == PEPeer.TRANSFERING) { final List peer_listeners_ref = peer_listeners_cow; if (peer_listeners_ref != null) { for (int i = 0; i < peer_listeners_ref.size(); i++) { final PEPeerListener peerListener = (PEPeerListener) peer_listeners_ref.get(i); peerListener.addAvailability(this, peerHavePieces); } availabilityAdded = true; } } } private void removeAvailability() { if (availabilityAdded && peerHavePieces != null) { final List peer_listeners_ref = peer_listeners_cow; if (peer_listeners_ref != null) { for (int i = 0; i < peer_listeners_ref.size(); i++) { final PEPeerListener peerListener = (PEPeerListener) peer_listeners_ref.get(i); peerListener.removeAvailability(this, peerHavePieces); } } availabilityAdded = false; } peerHavePieces = null; } protected void sendBTHandshake() { if (!handshake_sent) { connection.getOutgoingMessageQueue().addMessage(new BTHandshake(manager.getHash(), manager.getPeerId(), manager.isExtendedMessagingEnabled(), other_peer_handshake_version), false); } } // We could do this in a more automated way in future, but hardcoded is // simple and quick, // so we'll do that instead. :) static Map lt_ext_map = UTPeerExchange.ENABLED ? Collections.singletonMap("ut_pex", new Integer(1)) : Collections.EMPTY_MAP; private void sendLTHandshake() { String client_name = Constants.AZUREUS_NAME + " " + Constants.AZUREUS_VERSION; int localTcpPort = TCPNetworkManager.getSingleton().getTCPListeningPortNumber(); String tcpPortOverride = COConfigurationManager.getStringParameter("TCP.Listen.Port.Override"); try { localTcpPort = Integer.parseInt(tcpPortOverride); } catch (NumberFormatException e) { } // ignore as invalid input boolean require_crypto = NetworkManager.getCryptoRequired(manager.getAdapter().getCryptoLevel()); Map data_dict = new HashMap(); data_dict.put("m", lt_ext_map); data_dict.put("v", client_name); data_dict.put("p", new Integer(localTcpPort)); data_dict.put("e", new Long(require_crypto ? 1L : 0L)); LTHandshake lt_handshake = new LTHandshake(data_dict, other_peer_bt_lt_ext_version); connection.getOutgoingMessageQueue().addMessage(lt_handshake, false); } private void sendAZHandshake() { final Message[] avail_msgs = MessageManager.getSingleton().getRegisteredMessages(); final String[] avail_ids = new String[avail_msgs.length]; final byte[] avail_vers = new byte[avail_msgs.length]; for (int i = 0; i < avail_msgs.length; i++) { avail_ids[i] = avail_msgs[i].getID(); avail_vers[i] = avail_msgs[i].getVersion(); } int local_tcp_port = TCPNetworkManager.getSingleton().getTCPListeningPortNumber(); int local_udp_port = UDPNetworkManager.getSingleton().getUDPListeningPortNumber(); int local_udp2_port = UDPNetworkManager.getSingleton().getUDPNonDataListeningPortNumber(); String tcpPortOverride = COConfigurationManager.getStringParameter("TCP.Listen.Port.Override"); try { local_tcp_port = Integer.parseInt(tcpPortOverride); } catch (NumberFormatException e) { } // ignore as invalid input boolean require_crypto = NetworkManager.getCryptoRequired(manager.getAdapter().getCryptoLevel()); if (peerSessionID != null) Logger.log(new LogEvent(this, LOGID, LogEvent.LT_INFORMATION, "notifying peer of reconnect attempt")); AZHandshake az_handshake = new AZHandshake(AZPeerIdentityManager.getAZPeerIdentity(), mySessionID, peerSessionID, Constants.AZUREUS_NAME, Constants.AZUREUS_VERSION, local_tcp_port, local_udp_port, local_udp2_port, avail_ids, avail_vers, require_crypto ? AZHandshake.HANDSHAKE_TYPE_CRYPTO : AZHandshake.HANDSHAKE_TYPE_PLAIN, other_peer_handshake_version); connection.getOutgoingMessageQueue().addMessage(az_handshake, false); } @Override public int getPeerState() { return current_peer_state; } @Override public boolean isDownloadPossible() { if (!closing && !choked_by_other_peer) { if (lastNeededUndonePieceChange < piecePicker.getNeededUndonePieceChange()) { checkInterested(); lastNeededUndonePieceChange = piecePicker.getNeededUndonePieceChange(); } if (interested_in_other_peer && current_peer_state == PEPeer.TRANSFERING) return true; } return false; } @Override public int getPercentDoneInThousandNotation() { long total_done = getBytesDownloaded(); return (int) ((total_done * 1000) / diskManager.getTotalLength()); } @Override public boolean transferAvailable() { return (!choked_by_other_peer && interested_in_other_peer); } private void printRequestStats() { if (SHOW_DISCARD_RATE_STATS) { final float discard_perc = (requests_discarded * 100F) / ((requests_completed + requests_recovered + requests_discarded) * 1F); final float discard_perc_end = (requests_discarded_endgame * 100F) / ((requests_completed + requests_recovered + requests_discarded_endgame) * 1F); final float recover_perc = (requests_recovered * 100F) / ((requests_recovered + requests_discarded) * 1F); System.out.println("c=" + requests_completed + " d=" + requests_discarded + " de=" + requests_discarded_endgame + " r=" + requests_recovered + " dp=" + discard_perc + "% dpe=" + discard_perc_end + "% rp=" + recover_perc + "%"); } } /** * Checks if this peer is a seed or not by trivially checking if thier Have * bitflags exisits and shows a number of bits set equal to the torrent # of * pieces (and the torrent # of pieces is >0) */ private void checkSeed() { // seed implicitly means *something* to send (right?) if (peerHavePieces != null && nbPieces > 0) setSeed((peerHavePieces.nbSet == nbPieces)); else setSeed(false); } @Override public DiskManagerReadRequest request(final int pieceNumber, final int pieceOffset, final int pieceLength) { final DiskManagerReadRequest request = manager.createDiskManagerRequest(pieceNumber, pieceOffset, pieceLength); if (current_peer_state != TRANSFERING) { manager.requestCanceled(request); return null; } boolean added = false; try { requested_mon.enter(); if (!requested.contains(request)) { requested.add(request); added = true; } } finally { requested_mon.exit(); } if (added) { connection.getOutgoingMessageQueue().addMessage(new BTRequest(pieceNumber, pieceOffset, pieceLength, other_peer_request_version), false); _lastPiece = pieceNumber; try { recent_outgoing_requests_mon.enter(); recent_outgoing_requests.put(request, null); } finally { recent_outgoing_requests_mon.exit(); } return request; } return null; } @Override public int getRequestIndex(DiskManagerReadRequest request) { try { requested_mon.enter(); return (requested.indexOf(request)); } finally { requested_mon.exit(); } } @Override public void sendCancel(DiskManagerReadRequest request) { if (current_peer_state != TRANSFERING) return; if (hasBeenRequested(request)) { removeRequest(request); connection.getOutgoingMessageQueue().addMessage(new BTCancel(request.getPieceNumber(), request.getOffset(), request.getLength(), other_peer_cancel_version), false); } } @Override public void sendHave(int pieceNumber) { if (current_peer_state != TRANSFERING || pieceNumber == manager.getHiddenPiece()) return; // only force if the other peer doesn't have this piece and is not yet // interested or we;ve disabled // aggregation final boolean force = !other_peer_interested_in_me && peerHavePieces != null && !peerHavePieces.flags[pieceNumber]; outgoing_have_message_aggregator.queueHaveMessage(pieceNumber, force || have_aggregation_disabled); checkInterested(); } @Override public void sendChoke() { if (current_peer_state != TRANSFERING) return; if (mReceiptDispatcher != null) mReceiptDispatcher.check(false); // System.out.println( "["+(System.currentTimeMillis()/1000)+"] " // +connection + " choked"); connection.getOutgoingMessageQueue().addMessage(new BTChoke(other_peer_choke_version), false); choking_other_peer = true; is_optimistic_unchoke = false; if (outgoing_piece_message_handler != null) { outgoing_piece_message_handler.removeAllPieceRequests(); outgoing_piece_message_handler.destroy(); outgoing_piece_message_handler = null; } } @Override public void sendUnChoke() { if (current_peer_state != TRANSFERING) return; // System.out.println( "["+(System.currentTimeMillis()/1000)+"] " // +connection + " unchoked"); if (outgoing_piece_message_handler == null) { outgoing_piece_message_handler = new OutgoingBTPieceMessageHandler(this, connection.getOutgoingMessageQueue(), new OutgoingBTPieceMessageHandlerAdapter() { @Override public void diskRequestCompleted(long bytes) { peer_stats.diskReadComplete(bytes); } }, other_peer_piece_version); } choking_other_peer = false; // set this first as with pseudo peers we // can effectively synchronously act // on the unchoke advice and we don't want that borking with choked // still set connection.getOutgoingMessageQueue().addMessage(new BTUnchoke(other_peer_unchoke_version), false); } private void sendKeepAlive() { if (current_peer_state != TRANSFERING) return; if (outgoing_have_message_aggregator.hasPending()) { outgoing_have_message_aggregator.forceSendOfPending(); } else { connection.getOutgoingMessageQueue().addMessage(new BTKeepAlive(other_peer_keep_alive_version), false); } } private void sendMainlineDHTPort() { if (!this.ml_dht_enabled) { return; } MainlineDHTProvider provider = getDHTProvider(); if (provider == null) { return; } Message message = new BTDHTPort(provider.getDHTPort()); connection.getOutgoingMessageQueue().addMessage(message, false); } /** * Global checkInterested method. Early-out scan of pieces to determine if * the peer is interesting or not. They're interesting if they have a piece * that we Need and isn't Done */ @Override public void checkInterested() { if (closing || peerHavePieces == null || peerHavePieces.nbSet == 0) return; boolean is_interesting = false; if (piecePicker.hasDownloadablePiece()) { // there is a piece worth // being interested in if (!isSeed()) { // check individually if don't have all for (int i = peerHavePieces.start; i <= peerHavePieces.end; i++) { if (peerHavePieces.flags[i] && diskManager.isInteresting(i)) { is_interesting = true; break; } } } else is_interesting = true; } if (is_interesting && !interested_in_other_peer) connection.getOutgoingMessageQueue().addMessage(new BTInterested(other_peer_interested_version), false); else if (!is_interesting && interested_in_other_peer) connection.getOutgoingMessageQueue().addMessage(new BTUninterested(other_peer_uninterested_version), false); interested_in_other_peer = is_interesting; } /** * @deprecated no longer used by CVS code Checks if a particular piece makes * us interested in the peer * @param pieceNumber * the piece number that has been received */ /* * private void checkInterested(int pieceNumber) { if (closing) return; * // Do we need this piece and it's not Done? if * (!interested_in_other_peer &&diskManager.isInteresting(pieceNumber)) { * connection.getOutgoingMessageQueue().addMessage( new BTInterested(), * false ); interested_in_other_peer =true; } } */ /** * Private method to send the bitfield. */ private void sendBitField() { if (closing) return; // In case we're in super seed mode, we don't send our bitfield if (manager.isSuperSeedMode()) return; // create bitfield final DirectByteBuffer buffer = DirectByteBufferPool.getBuffer(DirectByteBuffer.AL_MSG, (nbPieces + 7) / 8); final DiskManagerPiece[] pieces = diskManager.getPieces(); int num_pieces = pieces.length; HashSet lazies = null; int[] lazy_haves = null; if (ENABLE_LAZY_BITFIELD) { int bits_in_first_byte = Math.min(num_pieces, 8); int last_byte_start_bit = (num_pieces / 8) * 8; int bits_in_last_byte = num_pieces - last_byte_start_bit; if (bits_in_last_byte == 0) { bits_in_last_byte = 8; last_byte_start_bit -= 8; } lazies = new HashSet(); // one bit from first byte int first_byte_entry = rnd.nextInt(bits_in_first_byte); if (pieces[first_byte_entry].isDone()) { lazies.add(new MutableInteger(first_byte_entry)); } // one bit from last byte int last_byte_entry = last_byte_start_bit + rnd.nextInt(bits_in_last_byte); if (pieces[last_byte_entry].isDone()) { lazies.add(new MutableInteger(last_byte_entry)); } // random others missing int other_lazies = rnd.nextInt(16) + 4; for (int i = 0; i < other_lazies; i++) { int random_entry = rnd.nextInt(num_pieces); if (pieces[random_entry].isDone()) { lazies.add(new MutableInteger(random_entry)); } } int num_lazy = lazies.size(); if (num_lazy == 0) { lazies = null; } else { lazy_haves = new int[num_lazy]; Iterator it = lazies.iterator(); for (int i = 0; i < num_lazy; i++) { int lazy_have = ((MutableInteger) it.next()).getValue(); lazy_haves[i] = lazy_have; } if (num_lazy > 1) { for (int i = 0; i < num_lazy; i++) { int swap = rnd.nextInt(num_lazy); if (swap != i) { int temp = lazy_haves[swap]; lazy_haves[swap] = lazy_haves[i]; lazy_haves[i] = temp; } } } } } int bToSend = 0; int i = 0; MutableInteger mi = new MutableInteger(0); int hidden_piece = manager.getHiddenPiece(); for (; i < num_pieces; i++) { if ((i % 8) == 0) { bToSend = 0; } bToSend = bToSend << 1; if (pieces[i].isDone() && i != hidden_piece) { if (lazies != null) { mi.setValue(i); if (lazies.contains(mi)) { // System.out.println( "LazySet: " + getIp() + " -> " + // i ); } else { bToSend += 1; } } else { bToSend += 1; } } if ((i % 8) == 7) { buffer.put(DirectByteBuffer.SS_BT, (byte) bToSend); } } if ((i % 8) != 0) { bToSend = bToSend << (8 - (i % 8)); buffer.put(DirectByteBuffer.SS_BT, (byte) bToSend); } buffer.flip(DirectByteBuffer.SS_BT); connection.getOutgoingMessageQueue().addMessage(new BTBitfield(buffer, other_peer_bitfield_version), false); if (lazy_haves != null) { final int[] f_lazy_haves = lazy_haves; SimpleTimer.addEvent("LazyHaveSender", SystemTime.getCurrentTime() + 1000 + rnd.nextInt(2000), new TimerEventPerformer() { int next_have = 0; @Override public void perform(TimerEvent event) { int lazy_have = f_lazy_haves[next_have++]; // System.out.println( "LazyDone: " + getIp() + " -> " + // lazy_have ); if (current_peer_state == TRANSFERING) { connection.getOutgoingMessageQueue().addMessage(new BTHave(lazy_have, other_peer_bt_have_version), false); if (next_have < f_lazy_haves.length && current_peer_state == TRANSFERING) { SimpleTimer.addEvent("LazyHaveSender", SystemTime.getCurrentTime() + rnd.nextInt(2000), this); } } } }); } } @Override public byte[] getId() { return peer_id; } @Override public String getIp() { return ip; } @Override public int getPort() { return port; } @Override public int getTCPListenPort() { return tcp_listen_port; } @Override public int getUDPListenPort() { return udp_listen_port; } @Override public int getUDPNonDataListenPort() { return (udp_non_data_port); } @Override public String getClient() { return client; } @Override public boolean isIncoming() { return incoming; } @Override public boolean isOptimisticUnchoke() { return is_optimistic_unchoke && !isChokedByMe(); } @Override public void setOptimisticUnchoke(boolean is_optimistic) { is_optimistic_unchoke = is_optimistic; } @Override public PEPeerControl getControl() { return manager; } @Override public PEPeerManager getManager() { return manager; } @Override public PEPeerStats getStats() { return peer_stats; } @Override public int[] getPriorityOffsets() { // normal peer has no special priority requirements return (null); } @Override public boolean requestAllocationStarts(int[] base_priorities) { return (false); } @Override public void requestAllocationComplete() { } /** * @return null if no bitfield has been recieved yet else returns BitFlags * indicating what pieces the peer has */ @Override public BitFlags getAvailable() { return peerHavePieces; } @Override public boolean isPieceAvailable(int pieceNumber) { if (peerHavePieces != null) return peerHavePieces.flags[pieceNumber]; return false; } @Override public boolean isChokingMe() { return choked_by_other_peer; } @Override public boolean isChokedByMe() { return choking_other_peer; } /** * @return true if the peer is interesting to us */ @Override public boolean isInteresting() { return interested_in_other_peer; } /** * @return true if the peer is interested in what we're offering */ @Override public boolean isInterested() { return other_peer_interested_in_me; } @Override public boolean isSeed() { return seed_set_by_accessor; } private void setSeed(boolean s) { if (seed_set_by_accessor != s) { seed_set_by_accessor = s; if (peer_exchange_item != null && s) { peer_exchange_item.seedStatusChanged(); } } } @Override public boolean isSnubbed() { return snubbed != 0; } @Override public long getSnubbedTime() { if (snubbed == 0) return 0; final long now = SystemTime.getCurrentTime(); if (now < snubbed) snubbed = now - 26; // odds are ... return now - snubbed; } @Override public void setSnubbed(boolean b) { if (!closing) { final long now = SystemTime.getCurrentTime(); if (!b) { if (snubbed != 0) { snubbed = 0; manager.decNbPeersSnubbed(); if (!choked_by_other_peer) unchokedTime = now; } } else if (snubbed == 0) { snubbed = now; manager.incNbPeersSnubbed(); if (!choked_by_other_peer) { final long unchoked = now - unchokedTime; if (unchoked > 0) unchokedTimeTotal += unchoked; } } } } @Override public void setUploadHint(int spreadTime) { spreadTimeHint = spreadTime; } @Override public int getUploadHint() { return spreadTimeHint; } @Override public void setUniqueAnnounce(int _uniquePiece) { uniquePiece = _uniquePiece; } @Override public int getUniqueAnnounce() { return uniquePiece; } /** To retreive arbitrary objects against a peer. */ @Override public Object getData(String key) { if (data == null) return null; return data.get(key); } /** To store arbitrary objects against a peer. */ @Override public void setData(String key, Object value) { try { general_mon.enter(); if (data == null) { data = new HashMap(); } if (value == null) { if (data.containsKey(key)) data.remove(key); } else { data.put(key, value); } } finally { general_mon.exit(); } } @Override public String getIPHostName() { if (ip_resolved == null) { ip_resolved = ip; ip_resolver_request = IPToHostNameResolver.addResolverRequest(ip_resolved, new IPToHostNameResolverListener() { @Override public final void IPResolutionComplete(String res, boolean ok) { ip_resolved = res; } }); } return (ip_resolved); } private void cancelRequests() { if (!closing) { // cancel any unsent requests in the queue final Message[] type = { new BTRequest(-1, -1, -1, other_peer_request_version) }; connection.getOutgoingMessageQueue().removeMessagesOfType(type, false); } if (requested != null && requested.size() > 0) { try { requested_mon.enter(); if (!closing) { // may have unchoked us, gotten a request, then // choked without filling it - snub them // if they actually have data coming in, they'll be // unsnubbed as soon as it writes final long timeSinceGoodData = getTimeSinceGoodDataReceived(); if (timeSinceGoodData == -1 || timeSinceGoodData > 60 * 1000) setSnubbed(true); } for (int i = requested.size() - 1; i >= 0; i--) { final DiskManagerReadRequest request = (DiskManagerReadRequest) requested.remove(i); manager.requestCanceled(request); } } finally { requested_mon.exit(); } } } @Override public int getMaxNbRequests() { return (-1); } @Override public int getNbRequests() { return requested.size(); } /** * * @return may be null for performance purposes */ @Override public List getExpiredRequests() { List result = null; // this is frequently called, hence we operate without a monitor and // take the hit of possible exceptions due to concurrent list // modification (only out-of-bounds can occur) try { for (int i = requested.size() - 1; i >= 0; i--) { final DiskManagerReadRequest request = (DiskManagerReadRequest) requested.get(i); if (request.isExpired()) { if (result == null) { result = new ArrayList(); } result.add(request); } } return (result); } catch (Throwable e) { return result; } } private boolean hasBeenRequested(DiskManagerReadRequest request) { try { requested_mon.enter(); return requested.contains(request); } finally { requested_mon.exit(); } } /** * @deprecated no longer used by CVS code */ @Deprecated protected void addRequest(DiskManagerReadRequest request) { try { requested_mon.enter(); requested.add(request); } finally { requested_mon.exit(); } _lastPiece = request.getPieceNumber(); } protected void removeRequest(DiskManagerReadRequest request) { try { requested_mon.enter(); requested.remove(request); } finally { requested_mon.exit(); } final BTRequest msg = new BTRequest(request.getPieceNumber(), request.getOffset(), request.getLength(), other_peer_request_version); connection.getOutgoingMessageQueue().removeMessage(msg, false); msg.destroy(); } private void resetRequestsTime(final long now) { try { requested_mon.enter(); final int requestedSize = requested.size(); for (int i = 0; i < requestedSize; i++) { final DiskManagerReadRequest request = (DiskManagerReadRequest) requested.get(i); if (request != null) request.resetTime(now); } } finally { requested_mon.exit(); } } @Override public String toString() { if (connection != null && connection.isConnected()) { return connection + (isTCP() ? " [" : "(UDP) [") + client + "]"; } return (isIncoming() ? "R: " : "L: ") + ip + ":" + port + (isTCP() ? " [" : "(UDP) [") + client + "]"; } public String getString() { return (toString()); } @Override public void doKeepAliveCheck() { final long now = SystemTime.getCurrentTime(); final long wait_time = now - last_message_sent_time; if (last_message_sent_time == 0 || wait_time < 0) { last_message_sent_time = now; // don't send if brand new // connection return; } if (wait_time > 2 * 60 * 1000) { // 2min keep-alive timer sendKeepAlive(); last_message_sent_time = now; // not quite true, but we don't want // to queue multiple keep-alives // before the first is actually sent } } @Override public boolean doTimeoutChecks() { // Timeouts for states PEPeerTransport.CONNECTION_PENDING and // PEPeerTransport.CONNECTION_CONNECTING are handled by the // ConnectDisconnectManager // so we don't need to deal with them here. final long now = SystemTime.getCurrentTime(); // make sure we time out stalled connections if (connection_state == PEPeerTransport.CONNECTION_FULLY_ESTABLISHED) { if (last_message_received_time > now) last_message_received_time = now; if (last_data_message_received_time > now) last_data_message_received_time = now; if (now - last_message_received_time > 5 * 60 * 1000 && now - last_data_message_received_time > 5 * 60 * 1000) { // 5min // timeout // assume this is due to a network failure // e.g. something that didn't close the TCP socket properly // will attempt reconnect closeConnectionInternally("timed out while waiting for messages", false, true); return true; } } // ensure we dont get stuck in the handshaking phases else if (connection_state == PEPeerTransport.CONNECTION_WAITING_FOR_HANDSHAKE) { if (connection_established_time > now) connection_established_time = now; else if (now - connection_established_time > 3 * 60 * 1000) { // 3min // timeout closeConnectionInternally("timed out while waiting for handshake"); return true; } } return false; } @Override public void doPerformanceTuningCheck() { Transport transport = connection.getTransport(); if (transport != null && peer_stats != null && outgoing_piece_message_handler != null) { // send speed -based tuning final long send_rate = peer_stats.getDataSendRate() + peer_stats.getProtocolSendRate(); if (send_rate >= 3125000) { // 25 Mbit/s transport.setTransportMode(Transport.TRANSPORT_MODE_TURBO); outgoing_piece_message_handler.setRequestReadAhead(256); } else if (send_rate >= 1250000) { // 10 Mbit/s transport.setTransportMode(Transport.TRANSPORT_MODE_TURBO); outgoing_piece_message_handler.setRequestReadAhead(128); } else if (send_rate >= 125000) { // 1 Mbit/s if (transport.getTransportMode() < Transport.TRANSPORT_MODE_FAST) { transport.setTransportMode(Transport.TRANSPORT_MODE_FAST); } outgoing_piece_message_handler.setRequestReadAhead(32); } else if (send_rate >= 62500) { // 500 Kbit/s outgoing_piece_message_handler.setRequestReadAhead(16); } else if (send_rate >= 31250) { // 250 Kbit/s outgoing_piece_message_handler.setRequestReadAhead(8); } else if (send_rate >= 12500) { // 100 Kbit/s outgoing_piece_message_handler.setRequestReadAhead(4); } else { outgoing_piece_message_handler.setRequestReadAhead(2); } // receive speed -based tuning final long receive_rate = peer_stats.getDataReceiveRate() + peer_stats.getProtocolReceiveRate(); if (receive_rate >= 1250000) { // 10 Mbit/s transport.setTransportMode(Transport.TRANSPORT_MODE_TURBO); } else if (receive_rate >= 125000) { // 1 Mbit/s if (transport.getTransportMode() < Transport.TRANSPORT_MODE_FAST) { transport.setTransportMode(Transport.TRANSPORT_MODE_FAST); } } } } @Override public int getConnectionState() { return connection_state; } @Override public long getTimeSinceLastDataMessageReceived() { if (last_data_message_received_time == -1) { // never received return -1; } final long now = SystemTime.getCurrentTime(); if (last_data_message_received_time > now) last_data_message_received_time = now; // time went backwards return now - last_data_message_received_time; } @Override public long getTimeSinceGoodDataReceived() { if (last_good_data_time == -1) return -1; // never received final long now = SystemTime.getCurrentTime(); if (last_good_data_time > now) last_good_data_time = now; // time went backwards return now - last_good_data_time; } @Override public long getTimeSinceLastDataMessageSent() { if (last_data_message_sent_time == -1) { // never sent return -1; } final long now = SystemTime.getCurrentTime(); if (last_data_message_sent_time > now) last_data_message_sent_time = now; // time went backwards return now - last_data_message_sent_time; } @Override public long getTimeSinceConnectionEstablished() { if (connection_established_time == 0) { // fudge it while the transport // is being connected return 0; } final long now = SystemTime.getCurrentTime(); if (connection_established_time > now) connection_established_time = now; return now - connection_established_time; } @Override public int getConsecutiveNoRequestCount() { return (consecutive_no_request_count); } @Override public void setConsecutiveNoRequestCount(int num) { consecutive_no_request_count = num; } protected void decodeReceiptRequests(ReceiptRequests message) { System.out.println("trying to decode receipt request: " + message); ReputationDAO rep = ReputationDAO.get(); try { mAttribution = message.getKeys().keySet(); Map<PublicKey, Float> keys = message.getKeys(); int doneSoFar = 0; List<Receipt> receipts = new LinkedList<Receipt>(); for (PublicKey k : keys.keySet().toArray(new PublicKey[0])) { try { long internal_id = rep.get_internal_id(k); // sanity check if (internal_id == 1) throw new IOException("received a request for receipts from ourself"); Receipt att = rep.get_latest_attestation_for_id(internal_id); assert att.getSigningID() == internal_id : "requested a receipt from an id and got one for a different one"; assert internal_id != rep.get_internal_id(getCertificate().getPublicKey()) : "some peer requested their own receipt"; if (att == null) { System.err.println("false positive: didn't have receipt for requested intermediary: " + KeyManipulation.concise(k.getEncoded()) + " " + message + " internal id: " + internal_id); } else { receipts.add(att); } } catch (IOException e) { System.err.println("error minting receipt, false positive? " + e.toString()); } doneSoFar++; if (doneSoFar > 30) // TODO: magic constant. we should figure // out a sensible way to negotiate this. break; } List<Integer> offsets = rep.compute_offsets_from_latest_receipts(receipts); ReceiptBundle bundle = new ReceiptBundle(receipts.toArray(new Receipt[0]), offsets, os_receipt_bundle); System.out.println("sending receipt bundle: " + bundle); connection.getOutgoingMessageQueue().addMessage(bundle, false); } catch (Exception e) { message.destroy(); closeConnectionInternally("Error decoding receipt requests: " + e); return; } } protected void decodeReceiptBundle(ReceiptBundle message) { System.out.println("attempting decode of receipt bundle: " + message); // requested_receipts ReputationDAO rep = ReputationDAO.get(); int[] offsets = message.getReceivedDueToRecoOffsets(); for (int i = 0; i < message.getReceipts().length; i++) { /** * Bind these together here. Because the reco_offset is not * serialized, we also need to do this in the persistent storage (in * case we are forced to delay verification due to unavailability) */ Receipt r = message.getReceipts()[i]; r.set_received_due_to_reco_offset(offsets[i]); try { long local_int_id = rep.get_internal_id(r.getSigningKey()); if (local_int_id == 1) throw new IOException("received a receipt bundle containing a receipt from us -- shouldn't happen"); if (requested_receipts.containsKey(local_int_id)) requested_receipts.put(local_int_id, r); else System.out.println("false positive dfg89df"); // TODO: // more // informative // accounting // here // System.out.println("successful decode (bound requested // receipts"); } catch (IOException e) { e.printStackTrace(); } } // If we're here, we haven't yet sent this but now one hop negiotiation // is fully complete this.initPostConnection(mHandshake); } protected void decodeCertificateExchange(CertificateExchange message) { System.out.println("got certificate exchange (bf has: " + message.getTopK().getStoredCount() + ")"); /** * 1. Add this peer to the database (or update existing entry) 2. Check * the bloom filter for shared intermediaries 3. Request receipts from * shared intermediaries (if this is a non-seed connection --- otherwise * we don't care about ROI) */ ReputationDAO rep = ReputationDAO.get(); long local_id = -1; // TODO: make this throw new IOException? if (mCertificate != null) closeConnectionInternally("got certificate exchange even though mCertificate != null!"); mCertificate = message.getCertificate(); mDirectAdvertisements = message.getAdvertisements(); try { // 1. Add/update DB local_id = rep.get_internal_id(message.getCertificate().getPublicKey()); rep.direct_observation(local_id, InetAddress.getByName(getIp()), getManager().getHash()); rep.update_soft_state(mCertificate.getPublicKey(), InetAddress.getByName(getIp()).getAddress(), getTCPListenPort(), getUDPListenPort(), new Date()); boolean sentReceiptReq = false; // 2. Check bloom filter System.out.println("checking bloom filter for shared ints: " + message.getTopK()); Map<PublicKey, Float> desired_intermediaries = Computation.desired_nodes_from_topK(message.getTopK()); // no need to ask for receipt from the signer desired_intermediaries.remove(getCertificate().getPublicKey()); // or from ourselves desired_intermediaries.remove(LocalIdentity.get().getKeys().getPublic()); if (desired_intermediaries.size() > 0) { // keep track of these so we'll know later if we're actually // getting receipts we asked for and not false positives for (PublicKey k : desired_intermediaries.keySet().toArray(new PublicKey[0])) { Long internal_id = rep.get_internal_id(k); if (internal_id == null) throw new IOException("internal id is null -- shouldn't happen"); assert internal_id != 1 : "we should have removed ourselves from desired intermediaries but didn't"; assert internal_id != rep.get_internal_id(getCertificate().getPublicKey()) : "we should have removed the remote host from desired but didn't"; requested_receipts.put(internal_id, null); } // 3. Request receipts ReceiptRequests req = new ReceiptRequests(desired_intermediaries, os_receipt_requests); System.out.println("sending receipt request: " + req); connection.getOutgoingMessageQueue().addMessage(req, false); sentReceiptReq = true; } // didn't find shared ints, so we can immediately move to exchange if (sentReceiptReq == false) { System.out.println("didn't find any shared ints, initPostConnnection() now"); this.initPostConnection(mHandshake); } } catch (IOException e) { message.destroy(); closeConnectionInternally("Error decoding certificate exchange: " + e); return; } } protected void decodeAttestation(Attestation inAttestation) { System.out.println("decoding attestation " + inAttestation); Receipt r = inAttestation.getReceipt(); r.setPreferredIntermediaries(requested_receipts.keySet()); ReputationDAO rep = ReputationDAO.get(); try { // HACK: we requested_receipts to record the offsets rep.record_attestation(inAttestation, requested_receipts); } catch (IOException e) { System.err.println(e); e.printStackTrace(); } } protected void decodeBTHandshake(BTHandshake handshake) { PeerIdentityDataID my_peer_data_id = manager.getPeerIdentityDataID(); if (!Arrays.equals(manager.getHash(), handshake.getDataHash())) { closeConnectionInternally("handshake has wrong infohash"); handshake.destroy(); return; } peer_id = handshake.getPeerId(); // Decode a client identification string from the given peerID this.client_peer_id = this.client = StringInterner.intern(PeerClassifier.getClientDescription(peer_id)); // make sure the client type is not banned if (!PeerClassifier.isClientTypeAllowed(client)) { closeConnectionInternally(client + " client type not allowed to connect, banned"); handshake.destroy(); return; } // make sure we are not connected to ourselves if (Arrays.equals(manager.getPeerId(), peer_id)) { manager.peerVerifiedAsSelf(this); // make sure we dont do it again closeConnectionInternally("given peer id matches myself"); handshake.destroy(); return; } // make sure we are not already connected to this peer boolean sameIdentity = PeerIdentityManager.containsIdentity(my_peer_data_id, peer_id, getPort()); boolean sameIP = false; // allow loopback connects for co-located proxy-based connections and // testing boolean same_allowed = COConfigurationManager.getBooleanParameter("Allow Same IP Peers") || ip.equals("127.0.0.1"); if (!same_allowed) { if (PeerIdentityManager.containsIPAddress(my_peer_data_id, ip)) { sameIP = true; } } if (sameIdentity) { boolean close = true; if (connection.isLANLocal()) { // this new connection is lan-local PEPeerTransport existing = manager.getTransportFromIdentity(peer_id); if (existing != null) { String existing_ip = existing.getIp(); // normally we don't allow a lan-local to replace a // lan-local connection. There is // however one exception - where the existing connection // comes from the gateway address // and therefore actually denotes an effectively // non-lan-local connection. Unfortunately // we don't have a good way of finding the default gateway, // so just go for ending in .1 if (!existing.isLANLocal() || (existing_ip.endsWith(".1") && !existing_ip.equals(ip))) { // so // drop // the // existing // connection // if // it // is // an // external // (non // lan-local) // one Debug.outNoStack("Dropping existing non-lanlocal peer connection [" + existing + "] in favour of [" + this + "]"); manager.removePeer(existing); close = false; } } } if (close) { closeConnectionInternally("peer matches already-connected peer id"); handshake.destroy(); return; } } if (sameIP) { closeConnectionInternally("peer matches already-connected IP address, duplicate connections not allowed"); handshake.destroy(); return; } // make sure we haven't reached our connection limit final int maxAllowed = manager.getMaxNewConnectionsAllowed(); if (maxAllowed == 0 && !manager.doOptimisticDisconnect(isLANLocal())) { final String msg = "too many existing peer connections [p" + PeerIdentityManager.getIdentityCount(my_peer_data_id) + "/g" + PeerIdentityManager.getTotalIdentityCount() + ", pmx" + PeerUtils.MAX_CONNECTIONS_PER_TORRENT + "/gmx" + PeerUtils.MAX_CONNECTIONS_TOTAL + "/dmx" + manager.getMaxConnections() + "]"; // System.out.println( msg ); closeConnectionInternally(msg); handshake.destroy(); return; } try { closing_mon.enter(); if (closing) { final String msg = "connection already closing"; closeConnectionInternally(msg); handshake.destroy(); return; } if (!PeerIdentityManager.addIdentity(my_peer_data_id, peer_id, getPort(), ip)) { closeConnectionInternally("peer matches already-connected peer id"); handshake.destroy(); return; } identityAdded = true; } finally { closing_mon.exit(); } if (Logger.isEnabled()) Logger.log(new LogEvent(this, LOGID, "In: has sent their handshake")); // Let's store the reserved bits somewhere so they can be examined later // (externally). handshake_reserved_bytes = handshake.getReserved(); /* * Waiting until we've received the initiating-end's full handshake, * before sending back our own, really should be the "proper" behavior. * However, classic BT trackers running NAT checking will only send the * first 48 bytes (up to infohash) of the peer handshake, skipping * peerid, which means we'll never get their complete handshake, and * thus never reply, which causes the NAT check to fail. So, we need to * send our handshake earlier, after we've verified the infohash. * * if( incoming ) { //wait until we've received their handshake before * sending ours sendBTHandshake(); } */ this.ml_dht_enabled = (handshake_reserved_bytes[7] & 1) == 1; messaging_mode = decideExtensionProtocol(handshake); // extended protocol processing if (messaging_mode == MESSAGING_AZMP) { /** * We log when a non-Azureus client claims to support extended * messaging... Obviously other Azureus clients do, so there's no * point logging about them! */ if (Logger.isEnabled() && client.indexOf("Azureus") == -1) { Logger.log(new LogEvent(this, LOGID, "Handshake claims extended AZ " + "messaging support... enabling AZ mode.")); } // Ignore the handshake setting - wait for the AZHandshake to // indicate // support instead. this.ml_dht_enabled = false; Transport transport = connection.getTransport(); boolean enable_padding = transport.isTCP() && transport.isEncrypted(); connection.getIncomingMessageQueue().setDecoder(new AZMessageDecoder()); connection.getOutgoingMessageQueue().setEncoder(new AZMessageEncoder(enable_padding)); // We will wait until we get the Az handshake before considering the // connection // initialised. this.sendAZHandshake(); handshake.destroy(); } else if (messaging_mode == MESSAGING_LTEP) { if (Logger.isEnabled()) { Logger.log(new LogEvent(this, LOGID, "Enabling LT extension protocol support...")); } connection.getIncomingMessageQueue().setDecoder(new LTMessageDecoder()); connection.getOutgoingMessageQueue().setEncoder(new LTMessageEncoder(this)); generateFallbackSessionId(); /** * We don't need to wait for the LT handshake, nor do we require it, * nor does it matter if the LT handshake comes later, nor does it * matter if it we receive it repeatedly. So there - we can * initialise the connection right now. :P */ this.initPostConnection(handshake); this.sendLTHandshake(); } else { this.client = ClientIdentifier.identifyBTOnly(this.client_peer_id, this.handshake_reserved_bytes); connection.getIncomingMessageQueue().getDecoder().resumeDecoding(); generateFallbackSessionId(); this.initPostConnection(handshake); } } private int decideExtensionProtocol(BTHandshake handshake) { boolean supports_azmp = (handshake.getReserved()[0] & 128) == 128; boolean supports_ltep = (handshake.getReserved()[5] & 16) == 16; if (!supports_azmp) { if (supports_ltep) { if (!manager.isExtendedMessagingEnabled()) { if (Logger.isEnabled()) { Logger.log(new LogEvent(this, LOGID, "Ignoring peer's LT extension protocol support," + " as disabled for this download.")); } return MESSAGING_BT_ONLY; // LTEP is supported, but // disabled. } return MESSAGING_LTEP; // LTEP is supported. } return MESSAGING_BT_ONLY; // LTEP isn't supported. } if (!supports_ltep) { // Check if it is AZMP enabled. if (!manager.isExtendedMessagingEnabled()) { if (Logger.isEnabled()) Logger.log(new LogEvent(this, LOGID, "Ignoring peer's extended AZ messaging support," + " as disabled for this download.")); return MESSAGING_BT_ONLY; } // Check if the client is misbehaving... else if (client.indexOf("Plus!") != -1) { if (Logger.isEnabled()) Logger.log(new LogEvent(this, LOGID, "Handshake mistakingly indicates" + " extended AZ messaging support...ignoring.")); return MESSAGING_BT_ONLY; } return MESSAGING_AZMP; } boolean enp_major_bit = (handshake.getReserved()[5] & 2) == 2; boolean enp_minor_bit = (handshake.getReserved()[5] & 1) == 1; // Only enable one of the blocks below. String their_ext_preference = ((enp_major_bit == enp_minor_bit) ? "Force " : "Prefer ") + ((enp_major_bit) ? "AZMP" : "LTEP"); // Force AZMP block. String our_ext_preference = "Force AZMP"; boolean use_azmp = enp_major_bit || enp_minor_bit; // Anything other // than Force LTEP, // then we force // AZMP to be used. boolean we_decide = use_azmp; // Prefer AZMP block (untested). /* * String our_ext_preference = "Prefer AZMP"; boolean use_azmp = * enp_major_bit; // Requires other client to prefer or force AZMP. * boolean we_decide = use_azmp && !enp_minor_bit; // We decide only if * we are using AZMP and the other client didn't force it. */ // Prefer LTEP block (untested). /* * String our_ext_preference = "Prefer LTEP"; boolean use_azmp = * enp_major_bit && enp_minor_bit; // Only use it Force AZMP is enabled. * boolean we_decide = enp_minor_bit && !use_azmp; // We decide only if * we are using LTEP and the other client didn't force it. */ if (Logger.isEnabled()) { String msg = "Peer supports both AZMP and LTEP: "; msg += "\"" + our_ext_preference + "\"" + (we_decide ? ">" : "<") + ((our_ext_preference.equals(their_ext_preference)) ? "= " : " "); msg += "\"" + their_ext_preference + "\" - using " + (use_azmp ? "AZMP" : "LTEP"); Logger.log(new LogEvent(this, LOGID, msg)); } return (use_azmp) ? MESSAGING_AZMP : MESSAGING_LTEP; } protected void decodeLTHandshake(LTHandshake handshake) { String lt_handshake_name = handshake.getClientName(); if (lt_handshake_name != null) { this.client_handshake = StringInterner.intern(lt_handshake_name); this.client = StringInterner.intern(ClientIdentifier.identifyLTEP(this.client_peer_id, this.client_handshake, this.peer_id)); } if (handshake.getTCPListeningPort() > 0) { // Only use crypto if it was specifically requested. Not sure what // the default // should be if they haven't indicated... Boolean crypto_requested = handshake.isCryptoRequested(); byte handshake_type = (crypto_requested != null && crypto_requested.booleanValue()) ? PeerItemFactory.HANDSHAKE_TYPE_CRYPTO : PeerItemFactory.HANDSHAKE_TYPE_PLAIN; tcp_listen_port = handshake.getTCPListeningPort(); peer_item_identity = PeerItemFactory.createPeerItem(ip, tcp_listen_port, PeerItem.convertSourceID(peer_source), handshake_type, udp_listen_port, // probably // none crypto_level, 0); } LTMessageEncoder encoder = (LTMessageEncoder) connection.getOutgoingMessageQueue().getEncoder(); encoder.updateSupportedExtensions(handshake.getExtensionMapping()); this.ut_pex_enabled = UTPeerExchange.ENABLED && encoder.supportsUTPEX(); /** * Grr... this is one thing which I'm sure I had figured out much better * than it is here... Basically, we "initialise" the connection at the * BT handshake stage, because the LT handshake is mandatory or required * to come first (unlike the AZ one). * * But when we receive an LT handshake, we have to "initialise" it like * we did previously, because we may have to set the internals up to * indicate if PEX is supported. * * I'm not entirely sure this method is meant to be called more than * once, and I'm less convinced that it's safe to do it repeatedly over * the lifetime of a properly-initialised, actually-doing-stuff * connection... but I'll worry about that later. */ this.doPostHandshakeProcessing(); handshake.destroy(); } protected void decodeAZHandshake(AZHandshake handshake) { // System.out.println("decoding handshake"); this.client_handshake = StringInterner.intern(handshake.getClient()); this.client_handshake_version = StringInterner.intern(handshake.getClientVersion()); this.client = StringInterner.intern(ClientIdentifier.identifyAZMP(this.client_peer_id, client_handshake, client_handshake_version, this.peer_id)); if (handshake.getTCPListenPort() > 0) { // use the ports given in // handshake tcp_listen_port = handshake.getTCPListenPort(); udp_listen_port = handshake.getUDPListenPort(); udp_non_data_port = handshake.getUDPNonDataListenPort(); final byte type = handshake.getHandshakeType() == AZHandshake.HANDSHAKE_TYPE_CRYPTO ? PeerItemFactory.HANDSHAKE_TYPE_CRYPTO : PeerItemFactory.HANDSHAKE_TYPE_PLAIN; // remake the id using the peer's remote listen port instead of // their random local port peer_item_identity = PeerItemFactory.createPeerItem(ip, tcp_listen_port, PeerItem.convertSourceID(peer_source), type, udp_listen_port, crypto_level, 0); } if (handshake.getReconnectSessionID() != null) { if (Logger.isEnabled()) { Logger.log(new LogEvent(this, LOGID, LogEvent.LT_INFORMATION, "received reconnect request ID: " + handshake.getReconnectSessionID().toBase32String())); } checkForReconnect(handshake.getReconnectSessionID()); } if (handshake.getRemoteSessionID() != null) peerSessionID = handshake.getRemoteSessionID(); else generateFallbackSessionId(); String[] supported_message_ids = handshake.getMessageIDs(); byte[] supported_message_versions = handshake.getMessageVersions(); // find mutually available message types final ArrayList messages = new ArrayList(); for (int i = 0; i < handshake.getMessageIDs().length; i++) { Message msg = MessageManager.getSingleton().lookupMessage(supported_message_ids[i]); if (msg != null) { // mutual support! messages.add(msg); String id = msg.getID(); byte supported_version = supported_message_versions[i]; // we can use == safely if (id == BTMessage.ID_BT_BITFIELD) other_peer_bitfield_version = supported_version; else if (id == BTMessage.ID_BT_CANCEL) other_peer_cancel_version = supported_version; else if (id == BTMessage.ID_BT_CHOKE) other_peer_choke_version = supported_version; else if (id == BTMessage.ID_BT_HANDSHAKE) other_peer_handshake_version = supported_version; else if (id == BTMessage.ID_BT_HAVE) other_peer_bt_have_version = supported_version; else if (id == BTMessage.ID_BT_INTERESTED) other_peer_interested_version = supported_version; else if (id == BTMessage.ID_BT_KEEP_ALIVE) other_peer_keep_alive_version = supported_version; else if (id == BTMessage.ID_BT_PIECE) other_peer_piece_version = supported_version; else if (id == BTMessage.ID_BT_UNCHOKE) other_peer_unchoke_version = supported_version; else if (id == BTMessage.ID_BT_UNINTERESTED) other_peer_uninterested_version = supported_version; else if (id == BTMessage.ID_BT_REQUEST) other_peer_request_version = supported_version; else if (id == AZMessage.ID_AZ_PEER_EXCHANGE) other_peer_pex_version = supported_version; else if (id == AZMessage.ID_AZ_REQUEST_HINT) other_peer_az_request_hint_version = supported_version; else if (id == AZMessage.ID_AZ_HAVE) other_peer_az_have_version = supported_version; else if (id == AZMessage.ID_AZ_BAD_PIECE) other_peer_az_bad_piece_version = supported_version; else if (id == BTMessage.ID_BT_DHT_PORT) this.ml_dht_enabled = true; /*************************************************************** * OneSwarm messages */ else if (id == CertificateExchange.ID_OS_CERT_EXCHANGE) os_certificate_exchange = supported_version; else if (id == ReceiptRequests.ID_OS_RECEIPT_REQUESTS) os_receipt_requests = supported_version; else if (id == ReceiptBundle.ID_OS_RECEIPT_BUNDLE) os_receipt_bundle = supported_version; else if (id == Attestation.ID_OS_ATTESTATION) os_attestation = supported_version; else { // we expect unmatched ones here at the moment as we're not // dealing with them yet or they don't make sense. // for example AZVER } } } supported_messages = (Message[]) messages.toArray(new Message[messages.size()]); if (outgoing_piece_message_handler != null) outgoing_piece_message_handler.setPieceVersion(other_peer_piece_version); outgoing_have_message_aggregator.setHaveVersion(other_peer_bt_have_version, other_peer_az_have_version); mIsOneSwarm = messages.contains(MessageManager.getSingleton().lookupMessage(CertificateExchange.ID_OS_CERT_EXCHANGE)) && messages.contains(MessageManager.getSingleton().lookupMessage(ReceiptRequests.ID_OS_RECEIPT_REQUESTS)) && messages.contains(MessageManager.getSingleton().lookupMessage(ReceiptBundle.ID_OS_RECEIPT_BUNDLE)) && messages.contains(MessageManager.getSingleton().lookupMessage(Attestation.ID_OS_ATTESTATION)); /** * If this is a OneSwarm connection, exchange certficiate / topK data */ if (mIsOneSwarm) { // System.out.println("Got OneSwarm connection " + this); mReceiptDispatcher = new ReceiptDispatcher(this); mHandshake = handshake; sendIDCert(); } else { // System.out.println("Connection is not OneSwarm: " + this); this.initPostConnection(handshake); } } private void sendIDCert() { System.out.println("Sending local identifying certificate to: " + this.getClient() + " / " + this.getIp()); try { LocalTopK topK = ReputationDAO.get().get_topK_by_obs(); PublicKey[] rand = null; PublicKey[] keys = ReputationDAO.get().get_frequently_observed(); Collections.shuffle(Arrays.asList(keys)); // TODO: magic constant rand = new PublicKey[Math.min(keys.length, 50)]; System.arraycopy(keys, 0, rand, 0, rand.length); /** * We only send a bloom filter if we aren't a seed (if we are -- we * really don't care about whether the remote peer values us, only * whether we value him) */ BloomFilter bf = topK.getBloomFilter(); if (getControl().isSeeding()) bf = LocalTopK.createTopK_BF(0); CertificateExchange certX = new CertificateExchange(LocalIdentity.get().getCertificate(), bf, rand, os_certificate_exchange); System.out.println("sending ID cert. topK: " + topK); connection.getOutgoingMessageQueue().addMessage(certX, false); } catch (IOException e) { closeConnectionInternally("Exception during certificate sending: " + e); } } private void initPostConnection(Message handshake) { changePeerState(PEPeer.TRANSFERING); connection_state = PEPeerTransport.CONNECTION_FULLY_ESTABLISHED; sendBitField(); handshake.destroy(); addAvailability(); sendMainlineDHTPort(); } protected void decodeBitfield(BTBitfield bitfield) { received_bitfield = true; final DirectByteBuffer field = bitfield.getBitfield(); final byte[] dataf = new byte[(nbPieces + 7) / 8]; if (field.remaining(DirectByteBuffer.SS_PEER) < dataf.length) { final String error = toString() + " has sent invalid Bitfield: too short [" + field.remaining(DirectByteBuffer.SS_PEER) + "<" + dataf.length + "]"; Debug.out(error); if (Logger.isEnabled()) Logger.log(new LogEvent(this, LOGID, LogEvent.LT_ERROR, error)); bitfield.destroy(); return; } field.get(DirectByteBuffer.SS_PEER, dataf); try { closing_mon.enter(); if (closing) bitfield.destroy(); else { final BitFlags tempHavePieces; if (peerHavePieces == null) { tempHavePieces = new BitFlags(nbPieces); } else { tempHavePieces = peerHavePieces; removeAvailability(); } for (int i = 0; i < nbPieces; i++) { final int index = i / 8; final int bit = 7 - (i % 8); final byte bData = dataf[index]; final byte b = (byte) (bData >> bit); if ((b & 0x01) == 1) { tempHavePieces.set(i); manager.updateSuperSeedPiece(this, i); } } bitfield.destroy(); peerHavePieces = tempHavePieces; addAvailability(); checkSeed(); checkInterested(); } } finally { closing_mon.exit(); } } protected void decodeMainlineDHTPort(BTDHTPort port) { int i_port = port.getDHTPort(); port.destroy(); if (!this.ml_dht_enabled) { return; } MainlineDHTProvider provider = getDHTProvider(); if (provider == null) { return; } try { provider.notifyOfIncomingPort(getIp(), i_port); } catch (Throwable t) { Debug.printStackTrace(t); } } protected void decodeChoke(BTChoke choke) { choke.destroy(); if (!choked_by_other_peer) { choked_by_other_peer = true; cancelRequests(); final long unchoked = SystemTime.getCurrentTime() - unchokedTime; if (unchoked > 0 && !isSnubbed()) unchokedTimeTotal += unchoked; } } protected void decodeUnchoke(BTUnchoke unchoke) { unchoke.destroy(); if (choked_by_other_peer) { choked_by_other_peer = false; if (!isSnubbed()) unchokedTime = SystemTime.getCurrentTime(); } } protected void decodeInterested(BTInterested interested) { interested.destroy(); // Don't allow known seeds to be interested in us other_peer_interested_in_me = !isSeed(); if (other_peer_interested_in_me && fast_unchoke_new_peers && isChokedByMe() && getData("fast_unchoke_done") == null) { setData("fast_unchoke_done", ""); sendUnChoke(); } } protected void decodeUninterested(BTUninterested uninterested) { uninterested.destroy(); other_peer_interested_in_me = false; // force send any pending haves in case one of them would make the other // peer interested again if (outgoing_have_message_aggregator != null) { outgoing_have_message_aggregator.forceSendOfPending(); } } protected void decodeHave(BTHave have) { final int pieceNumber = have.getPieceNumber(); have.destroy(); if ((pieceNumber >= nbPieces) || (pieceNumber < 0)) { closeConnectionInternally("invalid pieceNumber: " + pieceNumber); return; } if (closing) return; if (peerHavePieces == null) peerHavePieces = new BitFlags(nbPieces); if (!peerHavePieces.flags[pieceNumber]) { if (!interested_in_other_peer && diskManager.isInteresting(pieceNumber)) { connection.getOutgoingMessageQueue().addMessage(new BTInterested(other_peer_interested_version), false); interested_in_other_peer = true; } peerHavePieces.set(pieceNumber); final int pieceLength = manager.getPieceLength(pieceNumber); manager.havePiece(pieceNumber, pieceLength, this); checkSeed(); // maybe a seed using lazy bitfield, or suddenly // became a seed; other_peer_interested_in_me &= !isSeed(); // never consider seeds // interested peer_stats.hasNewPiece(pieceLength); } } protected void decodeAZHave(AZHave have) { final int[] pieceNumbers = have.getPieceNumbers(); have.destroy(); if (closing) { return; } if (peerHavePieces == null) { peerHavePieces = new BitFlags(nbPieces); } boolean send_interested = false; boolean new_have = false; for (int i = 0; i < pieceNumbers.length; i++) { int pieceNumber = pieceNumbers[i]; if ((pieceNumber >= nbPieces) || (pieceNumber < 0)) { closeConnectionInternally("invalid pieceNumber: " + pieceNumber); return; } if (!peerHavePieces.flags[pieceNumber]) { new_have = true; if (!(send_interested || interested_in_other_peer) && diskManager.isInteresting(pieceNumber)) { send_interested = true; } peerHavePieces.set(pieceNumber); final int pieceLength = manager.getPieceLength(pieceNumber); manager.havePiece(pieceNumber, pieceLength, this); peer_stats.hasNewPiece(pieceLength); } } if (new_have) { checkSeed(); // maybe a seed using lazy bitfield, or suddenly // became a seed; other_peer_interested_in_me &= !isSeed(); // never consider seeds // interested } if (send_interested) { connection.getOutgoingMessageQueue().addMessage(new BTInterested(other_peer_interested_version), false); interested_in_other_peer = true; } } protected long getBytesDownloaded() { if (peerHavePieces == null || peerHavePieces.flags.length == 0) return 0; final long total_done; if (peerHavePieces.flags[nbPieces - 1]) { total_done = ((long) (peerHavePieces.nbSet - 1) * diskManager.getPieceLength()) + diskManager.getPieceLength(nbPieces - 1); } else { total_done = (long) peerHavePieces.nbSet * diskManager.getPieceLength(); } return (Math.min(total_done, diskManager.getTotalLength())); } @Override public long getBytesRemaining() { return (diskManager.getTotalLength() - getBytesDownloaded()); } @Override public void sendBadPiece(int piece_number) { if (bad_piece_supported) { AZBadPiece bp = new AZBadPiece(piece_number, other_peer_az_bad_piece_version); connection.getOutgoingMessageQueue().addMessage(bp, false); } } protected void decodeAZBadPiece(AZBadPiece bad_piece) { final int piece_number = bad_piece.getPieceNumber(); bad_piece.destroy(); manager.badPieceReported(this, piece_number); } protected void decodeRequest(BTRequest request) { final int number = request.getPieceNumber(); final int offset = request.getPieceOffset(); final int length = request.getLength(); request.destroy(); if (!manager.validateReadRequest(this, number, offset, length)) { closeConnectionInternally("request for piece #" + number + ":" + offset + "->" + (offset + length - 1) + " is an invalid request"); return; } if (manager.getHiddenPiece() == number) { closeConnectionInternally("request for piece #" + number + " is invalid as piece is hidden"); return; } if (!choking_other_peer) { outgoing_piece_message_handler.addPieceRequest(number, offset, length); allowReconnect = true; } else { if (Logger.isEnabled()) Logger.log(new LogEvent(this, LOGID, "decodeRequest(): peer request for piece #" + number + ":" + offset + "->" + (offset + length - 1) + " ignored as peer is currently choked.")); } } protected void decodePiece(BTPiece piece) { final int pieceNumber = piece.getPieceNumber(); final int offset = piece.getPieceOffset(); final DirectByteBuffer payload = piece.getPieceData(); final int length = payload.remaining(DirectByteBuffer.SS_PEER); if (mReceiptDispatcher != null) mReceiptDispatcher.check(false); /* * if ( AEDiagnostics.CHECK_DUMMY_FILE_DATA ){ int pos = * payload.position( DirectByteBuffer.SS_PEER ); long off = * ((long)number) * getControl().getPieceLength(0) + offset; for (int * i=0;i<length;i++){ byte v = payload.get( DirectByteBuffer.SS_PEER ); * if ((byte)off != v ){ System.out.println( "piece: read is bad at " + * off + ": expected = " + (byte)off + ", actual = " + v ); break; } * off++; } payload.position( DirectByteBuffer.SS_PEER, pos ); } */ final Object error_msg = new Object() { @Override public final String toString() { return ("decodePiece(): Peer has sent piece #" + pieceNumber + ":" + offset + "->" + (offset + length - 1) + ", "); } }; if (!manager.validatePieceReply(this, pieceNumber, offset, payload)) { peer_stats.bytesDiscarded(length); manager.discarded(this, length); requests_discarded++; printRequestStats(); piece.destroy(); if (Logger.isEnabled()) Logger.log(new LogEvent(this, LOGID, LogEvent.LT_ERROR, error_msg + "but piece block discarded as invalid.")); return; } final DiskManagerReadRequest request = manager.createDiskManagerRequest(pieceNumber, offset, length); boolean piece_error = true; if (hasBeenRequested(request)) { // from active request removeRequest(request); final long now = SystemTime.getCurrentTime(); resetRequestsTime(now); if (manager.isWritten(pieceNumber, offset)) { // oops, looks like // this block has // already been // written peer_stats.bytesDiscarded(length); manager.discarded(this, length); if (manager.isInEndGameMode()) { // we're probably in // end-game mode then if (last_good_data_time != -1 && now - last_good_data_time <= 60 * 1000) setSnubbed(false); last_good_data_time = now; requests_discarded_endgame++; if (Logger.isEnabled()) Logger.log(new LogEvent(this, LogIDs.PIECES, LogEvent.LT_INFORMATION, error_msg + "but piece block ignored as already written in end-game mode.")); } else { // if they're not snubbed, then most likely this peer got a // re-request after some other peer // snubbed themselves, and the slow peer finially finished // the piece, but before this peer did // so give credit to this peer anyway for having delivered a // block at this time if (!isSnubbed()) last_good_data_time = now; if (Logger.isEnabled()) Logger.log(new LogEvent(this, LogIDs.PIECES, LogEvent.LT_WARNING, error_msg + "but piece block discarded as already written.")); requests_discarded++; } printRequestStats(); } else { // successfully received block! manager.writeBlock(pieceNumber, offset, payload, this, false); if (last_good_data_time != -1 && now - last_good_data_time <= 60 * 1000) setSnubbed(false); last_good_data_time = now; requests_completed++; piece_error = false; // dont destroy message, as we've passed // the payload on to the disk manager // for writing } } else { // initial request may have already expired, but check if we // can use the data anyway if (!manager.isWritten(pieceNumber, offset)) { final boolean ever_requested; try { recent_outgoing_requests_mon.enter(); ever_requested = recent_outgoing_requests.containsKey(request); } finally { recent_outgoing_requests_mon.exit(); } if (ever_requested) { // security-measure: we dont want to be // accepting any ol' random block manager.writeBlock(pieceNumber, offset, payload, this, true); final long now = SystemTime.getCurrentTime(); if (last_good_data_time != -1 && now - last_good_data_time <= 60 * 1000) setSnubbed(false); resetRequestsTime(now); last_good_data_time = now; requests_recovered++; printRequestStats(); piece_error = false; // dont destroy message, as we've // passed the payload on to the disk // manager for writing if (Logger.isEnabled()) Logger.log(new LogEvent(this, LogIDs.PIECES, LogEvent.LT_INFORMATION, error_msg + "expired piece block data recovered as useful.")); } else { System.out.println("[" + client + "]" + error_msg + "but expired piece block discarded as never requested."); peer_stats.bytesDiscarded(length); manager.discarded(this, length); requests_discarded++; printRequestStats(); if (Logger.isEnabled()) Logger.log(new LogEvent(this, LogIDs.PIECES, LogEvent.LT_ERROR, error_msg + "but expired piece block discarded as never requested.")); } } else { peer_stats.bytesDiscarded(length); manager.discarded(this, length); requests_discarded++; printRequestStats(); if (Logger.isEnabled()) Logger.log(new LogEvent(this, LogIDs.PIECES, LogEvent.LT_WARNING, error_msg + "but expired piece block discarded as already written.")); } } if (piece_error) piece.destroy(); else allowReconnect = true; } protected void decodeCancel(BTCancel cancel) { int number = cancel.getPieceNumber(); int offset = cancel.getPieceOffset(); int length = cancel.getLength(); cancel.destroy(); if (outgoing_piece_message_handler != null) outgoing_piece_message_handler.removePieceRequest(number, offset, length); } private void registerForMessageHandling() { // INCOMING MESSAGES connection.getIncomingMessageQueue().registerQueueListener(new IncomingMessageQueue.MessageQueueListener() { @Override public final boolean messageReceived(Message message) { //System.out.println("got msg: " + message + " " + PEPeerTransportProtocol.this.getIp()); if (Logger.isEnabled()) Logger.log(new LogEvent(PEPeerTransportProtocol.this, LogIDs.NET, "Received [" + message.getDescription() + "] message")); final long now = SystemTime.getCurrentTime(); last_message_received_time = now; if (message.getType() == Message.TYPE_DATA_PAYLOAD) { last_data_message_received_time = now; } String message_id = message.getID(); if (message_id.equals(BTMessage.ID_BT_PIECE)) { decodePiece((BTPiece) message); return true; } if (closing) { message.destroy(); return true; } if (message_id.equals(BTMessage.ID_BT_KEEP_ALIVE)) { message.destroy(); // make sure they're not spamming us if (!message_limiter.countIncomingMessage(message.getID(), 6, 60 * 1000)) { // allow // max // 6 // keep-alives // per // 60sec System.out.println(manager.getDisplayName() + ": Incoming keep-alive message flood detected, dropping spamming peer connection." + PEPeerTransportProtocol.this); closeConnectionInternally("Incoming keep-alive message flood detected, dropping spamming peer connection."); } return true; } if (message_id.equals(BTMessage.ID_BT_HANDSHAKE)) { decodeBTHandshake((BTHandshake) message); return true; } if (message_id.equals(AZMessage.ID_AZ_HANDSHAKE)) { decodeAZHandshake((AZHandshake) message); return true; } if (message_id.equals(CertificateExchange.ID_OS_CERT_EXCHANGE)) { decodeCertificateExchange((CertificateExchange) message); return true; } if (message_id.equals(ReceiptRequests.ID_OS_RECEIPT_REQUESTS)) { decodeReceiptRequests((ReceiptRequests) message); return true; } if (message_id.equals(ReceiptBundle.ID_OS_RECEIPT_BUNDLE)) { decodeReceiptBundle((ReceiptBundle) message); return true; } if (message_id.equals(Attestation.ID_OS_ATTESTATION)) { decodeAttestation((Attestation) message); return true; } if (message_id.equals(LTMessage.ID_LT_HANDSHAKE)) { decodeLTHandshake((LTHandshake) message); return true; } if (message_id.equals(BTMessage.ID_BT_BITFIELD)) { decodeBitfield((BTBitfield) message); return true; } if (message_id.equals(BTMessage.ID_BT_CHOKE)) { decodeChoke((BTChoke) message); if (choking_other_peer) { connection.enableEnhancedMessageProcessing(false); // downgrade // back // to // normal // handler } return true; } if (message_id.equals(BTMessage.ID_BT_UNCHOKE)) { decodeUnchoke((BTUnchoke) message); connection.enableEnhancedMessageProcessing(true); // make // sure // we // use a // fast // handler // for // the // resulting // download return true; } if (message_id.equals(BTMessage.ID_BT_INTERESTED)) { decodeInterested((BTInterested) message); return true; } if (message_id.equals(BTMessage.ID_BT_UNINTERESTED)) { decodeUninterested((BTUninterested) message); return true; } if (message_id.equals(BTMessage.ID_BT_HAVE)) { decodeHave((BTHave) message); return true; } if (message_id.equals(BTMessage.ID_BT_REQUEST)) { decodeRequest((BTRequest) message); return true; } if (message_id.equals(BTMessage.ID_BT_CANCEL)) { decodeCancel((BTCancel) message); return true; } if (message_id.equals(BTMessage.ID_BT_DHT_PORT)) { decodeMainlineDHTPort((BTDHTPort) message); return true; } if (message_id.equals(AZMessage.ID_AZ_PEER_EXCHANGE)) { decodePeerExchange((AZPeerExchange) message); return true; } if (message_id.equals(LTMessage.ID_UT_PEX)) { decodePeerExchange((UTPeerExchange) message); return true; } if (message_id.equals(AZMessage.ID_AZ_REQUEST_HINT)) { decodeAZRequestHint((AZRequestHint) message); return true; } if (message_id.equals(AZMessage.ID_AZ_HAVE)) { decodeAZHave((AZHave) message); return true; } if (message_id.equals(AZMessage.ID_AZ_BAD_PIECE)) { decodeAZBadPiece((AZBadPiece) message); return true; } return false; } @Override public final void protocolBytesReceived(int byte_count) { // update stats peer_stats.protocolBytesReceived(byte_count); manager.protocolBytesReceived(PEPeerTransportProtocol.this, byte_count); } ReputationDAO rep = ReputationDAO.get(); @Override public final void dataBytesReceived(int byte_count) { // Observe that the peer is sending data so that if theyre so // slow that the whole // data block times out, we don't think theyre not sending // anything at all last_data_message_received_time = SystemTime.getCurrentTime(); // update stats peer_stats.dataBytesReceived(byte_count); // PIAMOD -- record bytes received/sent in the DB if (isOneSwarm()) { try { Set<PublicKey> attrib = getAttribution(); if (attrib == null) rep.received_direct(rep.get_internal_id(mCertificate.getPublicKey()), byte_count); else { for (PublicKey k : attrib) rep.local_recv_due_to_remote_reco(rep.get_internal_id(k), byte_count / attrib.size()); } } catch (IOException e) { e.printStackTrace(); closeConnectionInternally("accounting error during receipt: " + e); } } manager.dataBytesReceived(PEPeerTransportProtocol.this, byte_count); } }); // OUTGOING MESSAGES connection.getOutgoingMessageQueue().registerQueueListener(new OutgoingMessageQueue.MessageQueueListener() { @Override public final boolean messageAdded(Message message) { return true; } @Override public final void messageQueued(Message message) { /* ignore */ } @Override public final void messageRemoved(Message message) { /* ignore */ } @Override public final void messageSent(Message message) { // update keep-alive info final long now = SystemTime.getCurrentTime(); last_message_sent_time = now; if (message.getType() == Message.TYPE_DATA_PAYLOAD) { last_data_message_sent_time = now; } if (message.getID().equals(BTMessage.ID_BT_UNCHOKE)) { // is // about // to // send // piece // data connection.enableEnhancedMessageProcessing(true); // so // make // sure // we // use a // fast // handler } else if (message.getID().equals(BTMessage.ID_BT_CHOKE)) { // is // done // sending // piece // data if (choked_by_other_peer) { connection.enableEnhancedMessageProcessing(false); // so // downgrade // back // to // normal // handler } } if (Logger.isEnabled()) Logger.log(new LogEvent(PEPeerTransportProtocol.this, LogIDs.NET, "Sent [" + message.getDescription() + "] message")); } @Override public final void protocolBytesSent(int byte_count) { // update stats peer_stats.protocolBytesSent(byte_count); manager.protocolBytesSent(PEPeerTransportProtocol.this, byte_count); } ReputationDAO rep = ReputationDAO.get(); @Override public final void dataBytesSent(int byte_count) { // update stats peer_stats.dataBytesSent(byte_count); manager.dataBytesSent(PEPeerTransportProtocol.this, byte_count); // PIAMOD -- record bytes received/sent in the DB if (isOneSwarm()) { try { Set<PublicKey> attrib = getAttribution(); if (attrib == null) rep.sent_direct(rep.get_internal_id(mCertificate.getPublicKey()), byte_count); else { for (PublicKey k : attrib) rep.local_sent_due_to_remote_reco(rep.get_internal_id(k), byte_count); } } catch (IOException e) { e.printStackTrace(); closeConnectionInternally("accounting error during receipt: " + e); } } } @Override public void flush() { } }); // start message processing connection.addRateLimiter(manager.getUploadLimitedRateGroup(), true); connection.addRateLimiter(manager.getDownloadLimitedRateGroup(), false); connection.startMessageProcessing(); } @Override public void addRateLimiter(LimitedRateGroup limiter, boolean upload) { connection.addRateLimiter(limiter, upload); } @Override public void removeRateLimiter(LimitedRateGroup limiter, boolean upload) { connection.removeRateLimiter(limiter, upload); } @Override public Connection getPluginConnection() { return plugin_connection; } @Override public Message[] getSupportedMessages() { return supported_messages; } @Override public boolean supportsMessaging() { return supported_messages != null; } @Override public int getMessagingMode() { return messaging_mode; } @Override public byte[] getHandshakeReservedBytes() { return this.handshake_reserved_bytes; } @Override public void setHaveAggregationEnabled(boolean enabled) { have_aggregation_disabled = !enabled; } @Override public boolean hasReceivedBitField() { return (received_bitfield); } @Override public String getEncryption() { Transport transport = connection.getTransport(); if (transport == null) { return (""); } return (transport.getEncryption()); } @Override public void addListener(PEPeerListener listener) { try { peer_listeners_mon.enter(); if (peer_listeners_cow == null) { peer_listeners_cow = new ArrayList(); } final List new_listeners = new ArrayList(peer_listeners_cow); new_listeners.add(listener); peer_listeners_cow = new_listeners; } finally { peer_listeners_mon.exit(); } } @Override public void removeListener(PEPeerListener listener) { try { peer_listeners_mon.enter(); if (peer_listeners_cow != null) { List new_listeners = new ArrayList(peer_listeners_cow); new_listeners.remove(listener); if (new_listeners.isEmpty()) { new_listeners = null; } peer_listeners_cow = new_listeners; } } finally { peer_listeners_mon.exit(); } } private void changePeerState(int new_state) { current_peer_state = new_state; if (current_peer_state == PEPeer.TRANSFERING) { // YUCK! doPostHandshakeProcessing(); } final List peer_listeners_ref = peer_listeners_cow; if (peer_listeners_ref != null) { for (int i = 0; i < peer_listeners_ref.size(); i++) { final PEPeerListener l = (PEPeerListener) peer_listeners_ref.get(i); l.stateChanged(this, current_peer_state); } } } private void doPostHandshakeProcessing() { // peer exchange registration if (manager.isPeerExchangeEnabled()) { // try and register all connections for their peer exchange info peer_exchange_item = manager.createPeerExchangeConnection(this); if (peer_exchange_item != null) { // check for peer exchange support if (ut_pex_enabled || peerSupportsMessageType(AZMessage.ID_AZ_PEER_EXCHANGE)) { peer_exchange_supported = true; } else { // no need to maintain internal states as we wont be // sending/receiving peer exchange messages peer_exchange_item.disableStateMaintenance(); } } } request_hint_supported = peerSupportsMessageType(AZMessage.ID_AZ_REQUEST_HINT); bad_piece_supported = peerSupportsMessageType(AZMessage.ID_AZ_BAD_PIECE); } private boolean peerSupportsMessageType(String message_id) { if (supported_messages != null) { for (int i = 0; i < supported_messages.length; i++) { if (supported_messages[i].getID().equals(message_id)) return true; } } return false; } @Override public void updatePeerExchange() { if (current_peer_state != TRANSFERING) return; if (!peer_exchange_supported) return; if (peer_exchange_item != null && manager.isPeerExchangeEnabled()) { final PeerItem[] adds = peer_exchange_item.getNewlyAddedPeerConnections(); final PeerItem[] drops = peer_exchange_item.getNewlyDroppedPeerConnections(); if ((adds != null && adds.length > 0) || (drops != null && drops.length > 0)) { if (ut_pex_enabled) { connection.getOutgoingMessageQueue().addMessage(new UTPeerExchange(adds, drops, (byte) 0), false); } else { connection.getOutgoingMessageQueue().addMessage(new AZPeerExchange(manager.getHash(), adds, drops, other_peer_pex_version), false); } } } } protected void decodePeerExchange(AZStylePeerExchange exchange) { final PeerItem[] added = exchange.getAddedPeers(); final PeerItem[] dropped = exchange.getDroppedPeers(); // make sure they're not spamming us if (!message_limiter.countIncomingMessage(exchange.getID(), 7, 120 * 1000)) { // allow // max // 7 // PEX // per // 2min // //TODO // reduce // max // after // 2308 // release? System.out.println(manager.getDisplayName() + ": Incoming PEX message flood detected, dropping spamming peer connection." + PEPeerTransportProtocol.this); closeConnectionInternally("Incoming PEX message flood detected, dropping spamming peer connection."); return; } exchange.destroy(); if ((added != null && added.length > exchange.getMaxAllowedPeersPerVolley(!this.has_received_initial_pex, true)) || (dropped != null && dropped.length > exchange.getMaxAllowedPeersPerVolley(!this.has_received_initial_pex, false))) { // drop these too-large messages as they seem to be used for DOS by // swarm poisoners closeConnectionInternally("Invalid PEX message received: too large, dropping likely poisoner peer connection."); return; } this.has_received_initial_pex = true; if (peer_exchange_supported && peer_exchange_item != null && manager.isPeerExchangeEnabled()) { if (added != null) { for (int i = 0; i < added.length; i++) { peer_exchange_item.addConnectedPeer(added[i]); } } if (dropped != null) { for (int i = 0; i < dropped.length; i++) { peer_exchange_item.dropConnectedPeer(dropped[i]); } } } else { if (Logger.isEnabled()) Logger.log(new LogEvent(this, LOGID, "Peer Exchange disabled for this download, " + "dropping received exchange message")); } } @Override public boolean sendRequestHint(int piece_number, int offset, int length, int life) { if (request_hint_supported) { AZRequestHint rh = new AZRequestHint(piece_number, offset, length, life, other_peer_az_request_hint_version); connection.getOutgoingMessageQueue().addMessage(rh, false); return (true); } else { return (false); } } protected void decodeAZRequestHint(AZRequestHint hint) { int piece_number = hint.getPieceNumber(); int offset = hint.getOffset(); int length = hint.getLength(); int life = hint.getLife(); hint.destroy(); if (life > REQUEST_HINT_MAX_LIFE) { life = REQUEST_HINT_MAX_LIFE; } if (manager.validateHintRequest(this, piece_number, offset, length)) { if (request_hint == null) { // we ignore life time currently as once hinted we don't accept // another hint // until that one is satisfied. This is to prevent too many // pieces starting request_hint = new int[] { piece_number, offset, length }; } } } @Override public int[] getRequestHint() { return (request_hint); } @Override public void clearRequestHint() { request_hint = null; } @Override public PeerItem getPeerItemIdentity() { return peer_item_identity; } @Override public int getReservedPieceNumber() { return reservedPiece; } @Override public void setReservedPieceNumber(int pieceNumber) { reservedPiece = pieceNumber; } @Override public int getIncomingRequestCount() { if (outgoing_piece_message_handler == null) { return (0); } return outgoing_piece_message_handler.getRequestCount(); } @Override public int getOutgoingRequestCount() { return (getNbRequests()); } @Override public int getOutboundDataQueueSize() { return (connection.getOutgoingMessageQueue().getTotalSize()); } @Override public boolean isStalledPendingLoad() { if (outgoing_piece_message_handler == null) { return (false); } return outgoing_piece_message_handler.isStalledPendingLoad(); } @Override public int[] getIncomingRequestedPieceNumbers() { if (outgoing_piece_message_handler == null) { return (new int[0]); } return outgoing_piece_message_handler.getRequestedPieceNumbers(); } @Override public int[] getOutgoingRequestedPieceNumbers() { try { requested_mon.enter(); /** * Cheap hack to reduce (but not remove all) the # of duplicate * entries */ int iLastNumber = -1; // allocate max size needed (we'll shrink it later) final int[] pieceNumbers = new int[requested.size()]; int pos = 0; for (int i = 0; i < requested.size(); i++) { DiskManagerReadRequest request = null; try { request = (DiskManagerReadRequest) requested.get(i); } catch (Exception e) { Debug.printStackTrace(e); } if (request != null && iLastNumber != request.getPieceNumber()) { iLastNumber = request.getPieceNumber(); pieceNumbers[pos++] = iLastNumber; } } final int[] trimmed = new int[pos]; System.arraycopy(pieceNumbers, 0, trimmed, 0, pos); return trimmed; } finally { requested_mon.exit(); } } @Override public int getPercentDoneOfCurrentIncomingRequest() { return (connection.getIncomingMessageQueue().getPercentDoneOfCurrentMessage()); } @Override public int getPercentDoneOfCurrentOutgoingRequest() { return (connection.getOutgoingMessageQueue().getPercentDoneOfCurrentMessage()); } /* * (non-Javadoc) * * @see org.gudy.azureus2.core3.logging.LogRelation#getLogRelationText() */ @Override public String getRelationText() { String text = ""; if (manager instanceof LogRelation) text = ((LogRelation) manager).getRelationText() + "; "; text += "Peer: " + toString(); return text; } /* * (non-Javadoc) * * @see org.gudy.azureus2.core3.logging.LogRelation#queryForClass(java.lang.Class) */ @Override public Object[] getQueryableInterfaces() { return new Object[] { manager }; } @Override public int getLastPiece() { return _lastPiece; } @Override public void setLastPiece(int pieceNumber) { _lastPiece = pieceNumber; } @Override public boolean isLANLocal() { if (connection == null) return (AddressUtils.isLANLocalAddress(ip) == AddressUtils.LAN_LOCAL_YES); return connection.isLANLocal(); } @Override public boolean isTCP() { return (connection.getEndpoint().getProtocols()[0].getType() == ProtocolEndpoint.PROTOCOL_TCP); } public long getUnchokedTimeTotal() { if (choked_by_other_peer) return unchokedTimeTotal; return unchokedTimeTotal + (SystemTime.getCurrentTime() - unchokedTime); } @Override public void setUploadRateLimitBytesPerSecond(int bytes) { connection.setUploadLimit(bytes); } @Override public void setDownloadRateLimitBytesPerSecond(int bytes) { connection.setDownloadLimit(bytes); } @Override public int getUploadRateLimitBytesPerSecond() { return connection.getUploadLimit(); } @Override public int getDownloadRateLimitBytesPerSecond() { return connection.getDownloadLimit(); } @Override public String getClientNameFromPeerID() { return this.client_peer_id; } @Override public String getClientNameFromExtensionHandshake() { if (!this.client_handshake.equals("") && !this.client_handshake_version.equals("")) { return this.client_handshake + " " + this.client_handshake_version; } return this.client_handshake; } private static MainlineDHTProvider getDHTProvider() { return AzureusCoreImpl.getSingleton().getGlobalManager().getMainlineDHTProvider(); } @Override public void generateEvidence(IndentWriter writer) { writer.println("ip=" + getIp() + ",in=" + isIncoming() + ",port=" + getPort() + ",cli=" + client + ",tcp=" + getTCPListenPort() + ",udp=" + getUDPListenPort() + ",oudp=" + getUDPNonDataListenPort() + ",p_state=" + getPeerState() + ",c_state=" + getConnectionState() + ",seed=" + isSeed() + ",pex=" + peer_exchange_supported + ",closing=" + closing); writer.println(" choked=" + choked_by_other_peer + ",choking=" + choking_other_peer + ",unchoke_time=" + unchokedTime + ", unchoke_total=" + unchokedTimeTotal + ",is_opt=" + is_optimistic_unchoke); writer.println(" interested=" + interested_in_other_peer + ",interesting=" + other_peer_interested_in_me + ",snubbed=" + snubbed); writer.println(" lp=" + _lastPiece + ",up=" + uniquePiece + ",rp=" + reservedPiece); writer.println(" last_sent=" + last_message_sent_time + "/" + last_data_message_sent_time + ",last_recv=" + last_message_received_time + "/" + last_data_message_received_time + "/" + last_good_data_time); writer.println(" conn_at=" + connection_established_time + ",cons_no_reqs=" + consecutive_no_request_count + ",discard=" + requests_discarded + "/" + requests_discarded_endgame + ",recov=" + requests_recovered + ",comp=" + requests_completed); } protected static class MutableInteger { private int value; protected MutableInteger(int v) { value = v; } protected void setValue(int v) { value = v; } protected int getValue() { return (value); } @Override public int hashCode() { return value; } @Override public boolean equals(Object obj) { if (obj instanceof MutableInteger) { return value == ((MutableInteger) obj).value; } return false; } } public Attestation sendAttestation(long inDiff) { if (mIsOneSwarm == false) { System.err.println("Trying to send attestation to non-oneswarm connection, shouldn't happen"); (new Exception()).printStackTrace(); return null; } try { System.out.println("sending attestation, diff: " + inDiff); Attestation attest = new Attestation(this, (int) inDiff, os_attestation); connection.getOutgoingMessageQueue().addMessage(attest, false); return attest; } catch (IOException e) { e.printStackTrace(); closeConnectionInternally("Couldn't send attestation: " + e.toString()); } return null; } }