/* * File : TRTrackerServerTorrent.java * Created : 26-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.tracker.server.impl; /** * @author parg * */ import java.util.*; import java.io.*; import java.net.InetAddress; import java.net.URL; import java.net.UnknownHostException; import org.gudy.azureus2.core3.logging.*; import org.gudy.azureus2.core3.tracker.server.*; import org.gudy.azureus2.core3.util.*; import com.aelitis.azureus.core.dht.netcoords.DHTNetworkPosition; public class TRTrackerServerTorrentImpl implements TRTrackerServerTorrent { private static final LogIDs LOGID = LogIDs.TRACKER; // no point in caching replies smaller than that below public static final int MIN_CACHE_ENTRY_SIZE = 10; public static final int MAX_UPLOAD_BYTES_PER_SEC = 3*1024*1024; //3MBs public static final int MAX_DOWNLOAD_BYTES_PER_SEC = MAX_UPLOAD_BYTES_PER_SEC; public static final boolean USE_LIGHTWEIGHT_SEEDS = true; public static final int MAX_IP_OVERRIDE_PEERS = 64; public static final byte COMPACT_MODE_NONE = 0; public static final byte COMPACT_MODE_NORMAL = 1; public static final byte COMPACT_MODE_AZ = 2; public static final byte COMPACT_MODE_AZ_2 = 3; public static final byte COMPACT_MODE_XML = 16; private static final int QUEUED_PEERS_MAX_SWARM_SIZE = 32; private static final int QUEUED_PEERS_MAX = 32; private static final int QUEUED_PEERS_ADD_MAX = 3; private TRTrackerServerImpl server; private HashWrapper hash; private Map<HashWrapper,TRTrackerServerPeerImpl> peer_map = new HashMap<HashWrapper,TRTrackerServerPeerImpl>(); private Map<String,TRTrackerServerPeerImpl> peer_reuse_map = new HashMap<String,TRTrackerServerPeerImpl>(); private List<TRTrackerServerPeerImpl> peer_list = new ArrayList<TRTrackerServerPeerImpl>(); private int peer_list_hole_count; private boolean peer_list_compaction_suspended; private List biased_peers = null; private int min_biased_peers = 0; private Map lightweight_seed_map = new HashMap(); private int seed_count; private int removed_count; private int ip_override_count; private int bad_NAT_count; // calculated periodically private Random random = new Random( SystemTime.getCurrentTime()); private long last_scrape_calc_time; private Map last_scrape; private LinkedHashMap announce_cache = new LinkedHashMap(); private TRTrackerServerTorrentStatsImpl stats; private List listeners = new ArrayList(); private List peer_listeners; private boolean deleted; private boolean enabled; private boolean map_size_diff_reported; private boolean ip_override_limit_exceeded_reported; private byte duplicate_peer_checker_index = 0; private byte[] duplicate_peer_checker = new byte[0]; private URL[] redirects; private boolean caching_enabled = true; private LinkedList queued_peers; protected AEMonitor this_mon = new AEMonitor( "TRTrackerServerTorrent" ); private List explicit_manual_biased_peers; private int explicit_next_peer; public TRTrackerServerTorrentImpl( TRTrackerServerImpl _server, HashWrapper _hash, boolean _enabled ) { server = _server; hash = _hash; enabled = _enabled; stats = new TRTrackerServerTorrentStatsImpl( this ); } public void setEnabled( boolean _enabled ) { enabled = _enabled; } public boolean isEnabled() { return( enabled ); } public void setMinBiasedPeers( int num ) { min_biased_peers = num; } public void importPeers( List peers ) { try{ this_mon.enter(); // only currently support import when torrent "empty" if ( peer_map.size() > 0 ){ System.out.println( "TRTrackerServerTorrent: ignoring peer import as torrent already active" ); return; } for (int i=0;i<peers.size();i++){ TRTrackerServerPeerImpl peer = TRTrackerServerPeerImpl.importPeer((Map)peers.get(i)); if ( peer != null ){ try{ String reuse_key = new String( peer.getIPAsRead(), Constants.BYTE_ENCODING ) + ":" + peer.getTCPPort(); peer_map.put( peer.getPeerId(), peer ); peer_list.add( peer ); peer_reuse_map.put( reuse_key, peer ); if ( peer.isSeed()){ seed_count++; } if ( peer.isBiased()){ if ( biased_peers == null ){ biased_peers = new ArrayList(); } biased_peers.add( peer ); } }catch( Throwable e ){ } } } }finally{ this_mon.exit(); } } public TRTrackerServerPeerImpl peerContact( String url_parameters, String event, HashWrapper peer_id, int tcp_port, int udp_port, int http_port, byte crypto_level, byte az_ver, String original_address, String ip_address, boolean ip_override, boolean loopback, String tracker_key, long uploaded, long downloaded, long left, long interval_requested, int up_speed, DHTNetworkPosition network_position ) throws TRTrackerServerException { if ( !enabled ){ throw( new TRTrackerServerException( "Torrent temporarily disabled" )); } // we can safely resolve the client_ip_address here as it is either already resolved or that of // the tracker. We need it resolved so we canonically store peers, otherwise we can get two // entries for a dns name and the corresponding ip if ( !HostNameToIPResolver.isNonDNSName( ip_address )){ try{ ip_address = HostNameToIPResolver.syncResolve( ip_address ).getHostAddress(); }catch( UnknownHostException e ){ } } TRTrackerServerException deferred_failure = null; try{ this_mon.enter(); handleRedirects( url_parameters, ip_address, false ); // System.out.println( "TRTrackerServerTorrent: peerContact, ip = " + ip_address ); int event_type = TRTrackerServerTorrentPeerListener.ET_UPDATED; if ( event != null && event.length() > 2){ char c = event.charAt(2); if ( c == 'm' ){ // "coMpleted" event_type = TRTrackerServerTorrentPeerListener.ET_COMPLETE; }else if ( c == 'o' ){ // "stOpped" event_type = TRTrackerServerTorrentPeerListener.ET_STOPPED; }else{ event_type = TRTrackerServerTorrentPeerListener.ET_STARTED; } } long now = SystemTime.getCurrentTime(); int tracker_key_hash_code = tracker_key==null?0:tracker_key.hashCode(); TRTrackerServerPeerImpl peer = (TRTrackerServerPeerImpl)peer_map.get( peer_id ); boolean new_peer = false; boolean peer_already_removed = false; boolean already_completed = false; long last_contact_time = 0; long ul_diff = 0; long dl_diff = 0; long le_diff = 0; byte[] ip_address_bytes = ip_address.getBytes( Constants.BYTE_ENCODING ); if ( peer == null ){ String reuse_key = new String( ip_address_bytes, Constants.BYTE_ENCODING ) + ":" + tcp_port; byte last_NAT_status = loopback?TRTrackerServerPeer.NAT_CHECK_OK:TRTrackerServerPeer.NAT_CHECK_UNKNOWN; new_peer = true; // check to see if this peer already has an entry against this torrent // and if so delete it (assumption is that the client has quit and // restarted with new peer id //System.out.println( "new peer" ); TRTrackerServerPeerImpl old_peer = (TRTrackerServerPeerImpl)peer_reuse_map.get( reuse_key ); if ( old_peer != null ){ // don't allow an ip_override to grab a non-override entry as this is a way for // a malicious client to remove, say, a seed (simply send in a "stopped" command // with override set to the seed you want to remove) if ( ip_override && !old_peer.isIPOverride()){ throw( new TRTrackerServerException( "IP Override denied (existing '" + reuse_key + "' is not override)" )); } last_contact_time = old_peer.getLastContactTime(); already_completed = old_peer.getDownloadCompleted(); removePeer( old_peer, TRTrackerServerTorrentPeerListener.ET_REPLACED, null ); lightweight_seed_map.remove( old_peer.getPeerId()); }else{ lightweightSeed lws = (lightweightSeed)lightweight_seed_map.remove( peer_id ); if ( lws != null ){ last_contact_time = lws.getLastContactTime(); ul_diff = uploaded - lws.getUploaded(); if ( ul_diff < 0 ){ ul_diff = 0; } last_NAT_status = lws.getNATStatus(); }else{ last_contact_time = now; } } if ( event_type != TRTrackerServerTorrentPeerListener.ET_STOPPED ){ Set biased_peer_set = server.getBiasedPeers(); boolean biased = biased_peer_set != null && biased_peer_set.contains( ip_address ); if ( ip_override && ip_override_count >= MAX_IP_OVERRIDE_PEERS && !( loopback || biased )){ // bail out - the peer will still get an announce response but we don't // want too many override peers on a torrent as these can be spoofed // to cause trouble if ( !ip_override_limit_exceeded_reported ){ ip_override_limit_exceeded_reported = true; Debug.out( "Too many ip-override peers for " + ByteFormatter.encodeString( hash.getBytes())); } return( null ); } peer = new TRTrackerServerPeerImpl( peer_id, tracker_key_hash_code, ip_address_bytes, ip_override, tcp_port, udp_port, http_port, crypto_level, az_ver, last_contact_time, already_completed, last_NAT_status, up_speed, network_position ); if ( ip_override ){ // never allow an ip-override to take on the guise of a biased peer if ( biased ){ // UNLESS the originating IP is biased too if ( !biased_peer_set.contains( original_address )){ throw( new TRTrackerServerException( "IP Override denied (you are " + original_address + ")" )); } } ip_override_count++; } peer_map.put( peer_id, peer ); peer_list.add( peer ); peer_reuse_map.put( reuse_key, peer ); if ( biased ){ peer.setBiased( true ); if ( biased_peers == null ){ biased_peers = new ArrayList(); } biased_peers.add( peer ); } if ( queued_peers != null ){ if ( peer_map.size() > QUEUED_PEERS_MAX_SWARM_SIZE ){ queued_peers = null; }else{ // peer has become active, remove the queued peer info Iterator it = queued_peers.iterator(); while( it.hasNext()){ QueuedPeer qp = (QueuedPeer)it.next(); if ( qp.sameAs( peer )){ it.remove(); break; } } } } } }else{ int existing_tracker_key_hash_code = peer.getKeyHashCode(); // System.out.println( "tracker_key:" + existing_tracker_key + "/" + tracker_key ); if ( existing_tracker_key_hash_code != tracker_key_hash_code ){ if ( server.isKeyEnabled()){ throw( new TRTrackerServerException( "Unauthorised: key mismatch ")); } } if ( ip_override ){ // biased peers are never ip-override if ( peer.isBiased()){ // UNLESS the originating IP is biased too Set biased_peer_set = server.getBiasedPeers(); if ( biased_peer_set == null || !biased_peer_set.contains( original_address )){ throw( new TRTrackerServerException( "IP Override denied (you are " + original_address + ")")); } } // prevent an ip_override peer from affecting a non-override entry if ( !peer.isIPOverride()){ throw( new TRTrackerServerException( "IP Override denied (existing entry not override)" )); } } already_completed = peer.getDownloadCompleted(); last_contact_time = peer.getLastContactTime(); if ( event_type == TRTrackerServerTorrentPeerListener.ET_STOPPED ){ removePeer( peer, event_type, url_parameters ); peer_already_removed = true; }else{ // IP may have changed - update if required // it is possible for two az clients to have the same peer id. Unlikely but possible // or indeed some hacked versions could do it on purpose. If this is the case then all we // will see here is address/port changes as each peer announces byte[] old_ip = peer.getIPAsRead(); int old_port = peer.getTCPPort(); if ( peer.update( ip_address_bytes, tcp_port, udp_port, http_port, crypto_level, az_ver, up_speed, network_position )){ // same peer id so same port String old_key = new String( old_ip, Constants.BYTE_ENCODING ) + ":" + old_port; String new_key = new String( ip_address_bytes, Constants.BYTE_ENCODING ) + ":" + tcp_port; // it is possible, on address change, that the target address already exists and is // (was) being used by another peer. Given that this peer has taken over its address // the assumption is that the other peer has also had an address change and has yet // to report it. The only action here is to delete the other peer TRTrackerServerPeerImpl old_peer = (TRTrackerServerPeerImpl)peer_reuse_map.get( new_key ); if ( old_peer != null ){ removePeer( old_peer, TRTrackerServerTorrentPeerListener.ET_REPLACED, null ); } // now swap the keys if ( peer_reuse_map.remove( old_key ) == null ){ Debug.out( "TRTrackerServerTorrent: IP address change: '" + old_key + "' -> '" + new_key + "': old key not found" ); } peer_reuse_map.put( new_key, peer ); } } } // a null peer here signifies a new peer whose first state was "stopped" long new_timeout = now + ( interval_requested * 1000 * TRTrackerServerImpl.CLIENT_TIMEOUT_MULTIPLIER ); if ( peer != null ){ peer.setTimeout( now, new_timeout ); // if this is the first time we've heard from this peer then we don't want to // use existing ul/dl value diffs as they will have been reported previously // (either the client's changed peer id by stop/start (in which case the values // should be 0 anyway as its a per-session total), or the tracker's been // stopped and started). if ( !new_peer ){ ul_diff = uploaded - peer.getUploaded(); dl_diff = downloaded - peer.getDownloaded(); } // simple rate control long elapsed_time = now - last_contact_time; if ( elapsed_time == 0 ){ elapsed_time = SystemTime.TIME_GRANULARITY_MILLIS; } long ul_rate = (ul_diff*1000)/elapsed_time; // bytes per second long dl_rate = (dl_diff*1000)/elapsed_time; if ( ul_rate > MAX_UPLOAD_BYTES_PER_SEC ){ if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, "TRTrackerPeer: peer " + peer.getIPRaw() + "/" + new String(peer.getPeerId().getHash()) + " reported an upload rate of " + ul_rate / 1024 + " KiB/s per second")); ul_diff = 0; } if ( dl_rate > MAX_DOWNLOAD_BYTES_PER_SEC ){ if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, "TRTrackerPeer: peer " + peer.getIPRaw() + "/" + new String(peer.getPeerId().getHash()) + " reported a download rate of " + dl_rate / 1024 + " KiB/s per second")); dl_diff = 0; } // when the peer is removed its "left" amount will dealt with le_diff = (event_type==TRTrackerServerTorrentPeerListener.ET_STOPPED)?0:(left - peer.getAmountLeft()); boolean was_seed = new_peer?false:peer.isSeed(); peer.setStats( uploaded, downloaded, left ); boolean is_seed = peer.isSeed(); if (!(event_type == TRTrackerServerTorrentPeerListener.ET_STOPPED || was_seed || !is_seed )){ seed_count++; } // report event *after* updating totals above so listeners get a valid initial // view of the peer (e.g. is it a seed) // if the peer has already been removed above then it will have reported the // event already if ( !peer_already_removed ){ try{ peerEvent( peer, event_type, url_parameters ); }catch( TRTrackerServerException e ){ deferred_failure = e; } } } stats.addAnnounce( ul_diff, dl_diff, le_diff, peer != null && peer.isBiased()); if ( event_type==TRTrackerServerTorrentPeerListener.ET_COMPLETE && !already_completed ){ peer.setDownloadCompleted(); stats.addCompleted(); } if ( peer != null && peer.isSeed()){ int seed_limit = TRTrackerServerImpl.getSeedLimit(); if ( seed_limit != 0 && seed_count > seed_limit && !loopback ){ if ( !peer_already_removed ){ removePeer( peer, TRTrackerServerTorrentPeerListener.ET_TOO_MANY_PEERS, null ); } // this is picked up by AZ client removal rules and causes the torrent to // be removed throw( new TRTrackerServerException( "too many seeds" )); } int seed_retention = TRTrackerServerImpl.getMaxSeedRetention(); if ( seed_retention != 0 && seed_count > seed_retention ){ // remove 5% of the seeds int to_remove = (seed_retention/20)+1; try{ peer_list_compaction_suspended = true; // remove bad NAT ones in preference to others for (int bad_nat_loop=TRTrackerServerNATChecker.getSingleton().isEnabled()?0:1;bad_nat_loop<2;bad_nat_loop++){ for (int i=0;i<peer_list.size();i++){ TRTrackerServerPeerImpl this_peer = (TRTrackerServerPeerImpl)peer_list.get(i); if ( this_peer != null && this_peer.isSeed() && !this_peer.isBiased()){ boolean bad_nat = this_peer.isNATStatusBad(); if ( ( bad_nat_loop == 0 && bad_nat ) || ( bad_nat_loop == 1 )){ if ( USE_LIGHTWEIGHT_SEEDS ){ lightweight_seed_map.put( this_peer.getPeerId(), new lightweightSeed( now, new_timeout, this_peer.getUploaded(), this_peer.getNATStatus())); } removePeer( this_peer, i, TRTrackerServerTorrentPeerListener.ET_TOO_MANY_PEERS, null ); if ( --to_remove == 0 ){ break; } } } } if ( to_remove == 0 ){ break; } } }finally{ peer_list_compaction_suspended = false; } checkForPeerListCompaction( false ); } } if ( deferred_failure != null ){ if ( peer != null && !peer_already_removed ){ removePeer( peer, TRTrackerServerTorrentPeerListener.ET_FAILED, url_parameters ); } throw( deferred_failure ); } return( peer ); }catch( UnsupportedEncodingException e ){ throw( new TRTrackerServerException( "Encoding fails", e )); }finally{ // note we can bail out here through a return when there are too many IP overrides this_mon.exit(); } } public void peerQueued( String ip, int tcp_port, int udp_port, int http_port, byte crypto_level, byte az_ver, long timeout_secs, boolean seed ) { // System.out.println( "peerQueued: " + ip + "/" + tcp_port + "/" + udp_port + "/" + crypto_level ); if ( peer_map.size() >= QUEUED_PEERS_MAX_SWARM_SIZE || tcp_port == 0 ){ return; } try{ this_mon.enter(); Set biased_peer_set = server.getBiasedPeers(); boolean biased = biased_peer_set != null && biased_peer_set.contains( ip ); QueuedPeer new_qp = new QueuedPeer( ip, tcp_port, udp_port, http_port, crypto_level, az_ver, (int)timeout_secs, seed, biased ); String reuse_key = new_qp.getIP() + ":" + tcp_port; // if still active then drop it if ( peer_reuse_map.containsKey( reuse_key )){ return; } boolean add = true; if ( queued_peers != null ){ Iterator it = queued_peers.iterator(); while( it.hasNext()){ QueuedPeer qp = (QueuedPeer)it.next(); if ( qp.sameAs( new_qp )){ it.remove(); queued_peers.add( new_qp ); return; } } if ( queued_peers.size() >= QUEUED_PEERS_MAX ){ QueuedPeer oldest = null; it = queued_peers.iterator(); while( it.hasNext()){ QueuedPeer qp = (QueuedPeer)it.next(); // never drop biased peers if ( qp.isBiased()){ continue; } if ( oldest == null ){ oldest = qp; }else{ if ( qp.getCreateTime() < oldest.getCreateTime()){ oldest = qp; } } } if ( oldest == null ){ add = false; }else{ queued_peers.remove( oldest ); } } }else{ queued_peers = new LinkedList(); } if ( add ){ queued_peers.addFirst( new_qp ); } }finally{ this_mon.exit(); } } public void remove( TRTrackerServerPeerBase peer ) { try{ this_mon.enter(); if ( peer instanceof TRTrackerServerPeerImpl ){ TRTrackerServerPeerImpl pi = (TRTrackerServerPeerImpl)peer; if ( peer_map.containsKey( pi.getPeerId())){ int index = peer_list.indexOf( pi ); if ( index != -1 ){ removePeer( pi, index, TRTrackerServerTorrentPeerListener.ET_FAILED, null ); } } }else{ if ( queued_peers != null ){ queued_peers.remove( peer ); if ( queued_peers.size() == 0 ){ queued_peers = null; } } } }finally{ this_mon.exit(); } } protected void removePeer( TRTrackerServerPeerImpl peer, int reason, String url_parameters ) { removePeer( peer, -1, reason, url_parameters ); } protected void removePeer( TRTrackerServerPeerImpl peer, int peer_list_index, int reason, String url_parameters ) // -1 if not known { try{ this_mon.enter(); if ( peer.isIPOverride()){ ip_override_count--; } stats.removeLeft( peer.getAmountLeft()); if ( peer_map.size() != peer_reuse_map.size()){ if ( !map_size_diff_reported ){ map_size_diff_reported = true; Debug.out( "TRTrackerServerTorrent::removePeer: maps size different ( " + peer_map.size() + "/" + peer_reuse_map.size() +")"); } } { Object o = peer_map.remove( peer.getPeerId()); if ( o == null ){ Debug.out(" TRTrackerServerTorrent::removePeer: peer_map doesn't contain peer"); }else{ try{ peerEvent( peer, reason, url_parameters ); }catch( TRTrackerServerException e ){ // ignore during peer removal } } } if ( peer_list_index == -1 ){ int peer_index = peer_list.indexOf( peer ); if ( peer_index == -1){ Debug.out(" TRTrackerServerTorrent::removePeer: peer_list doesn't contain peer"); }else{ peer_list.set( peer_index, null ); } }else{ if ( peer_list.get( peer_list_index ) == peer ){ peer_list.set( peer_list_index, null ); }else{ Debug.out(" TRTrackerServerTorrent::removePeer: peer_list doesn't contain peer at index"); } } peer_list_hole_count++; checkForPeerListCompaction( false ); try{ Object o = peer_reuse_map.remove( new String( peer.getIPAsRead(), Constants.BYTE_ENCODING ) + ":" + peer.getTCPPort()); if ( o == null ){ Debug.out(" TRTrackerServerTorrent::removePeer: peer_reuse_map doesn't contain peer"); } }catch( UnsupportedEncodingException e ){ } if ( biased_peers != null ){ biased_peers.remove( peer ); } if ( peer.isSeed()){ seed_count--; } removed_count++; }finally{ this_mon.exit(); } } protected void updateBiasedPeers( Set biased_peers_set ) { try{ this_mon.enter(); Iterator it = peer_list.iterator(); if ( it.hasNext() && biased_peers == null ){ biased_peers = new ArrayList(); } while( it.hasNext()){ TRTrackerServerPeerImpl this_peer = (TRTrackerServerPeerImpl)it.next(); if ( this_peer != null ){ boolean biased = biased_peers_set.contains( this_peer.getIPRaw()); this_peer.setBiased( biased ); if ( biased ){ if ( !biased_peers.contains( this_peer )){ biased_peers.add( this_peer ); } }else{ biased_peers.remove( this_peer ); } } } if ( queued_peers != null ){ it = queued_peers.iterator(); while( it.hasNext()){ QueuedPeer peer = (QueuedPeer)it.next(); peer.setBiased( biased_peers_set.contains( peer.getIP())); } } }finally{ this_mon.exit(); } } public TRTrackerServerTorrent addLink( String link ) { return( server.addLink( link, this )); } public void removeLink( String link ) { server.removeLink( link, this ); } public Map exportAnnounceToMap( String ip_address, HashMap preprocess_map, TRTrackerServerPeerImpl requesting_peer, // maybe null for an initial announce from a stopped peer boolean include_seeds, int num_want, long interval, long min_interval, boolean no_peer_id, byte compact_mode, byte crypto_level, DHTNetworkPosition network_position ) { try{ this_mon.enter(); long now = SystemTime.getCurrentTime(); // we have to force non-caching for nat_warnings responses as they include // peer-specific data boolean nat_warning = requesting_peer != null && requesting_peer.getNATStatus() == TRTrackerServerPeerImpl.NAT_CHECK_FAILED; int total_peers = peer_map.size(); int cache_millis = TRTrackerServerImpl.getAnnounceCachePeriod(); boolean send_peer_ids = TRTrackerServerImpl.getSendPeerIds(); // override if client has explicitly not requested them if ( no_peer_id || compact_mode != COMPACT_MODE_NONE ){ send_peer_ids = false; } boolean add_to_cache = false; int max_peers = TRTrackerServerImpl.getMaxPeersToSend(); // num_want < 0 -> not supplied so give them max if ( num_want < 0 ){ num_want = total_peers; } // trim back to max_peers if specified if ( max_peers > 0 && num_want > max_peers ){ num_want = max_peers; } // if set this list contains the only peers that are to be returned. It allows a manual // external peer selection algorithm List<TRTrackerServerSimplePeer> explicit_limited_peers = null; List<TRTrackerServerSimplePeer> explicit_biased_peers = null; Set remove_ips = null; if ( requesting_peer != null ){ if ( peer_listeners != null ){ for (int i=0;i<peer_listeners.size();i++){ try{ Map reply = ((TRTrackerServerTorrentPeerListener)peer_listeners.get(i)).eventOccurred( this, requesting_peer, TRTrackerServerTorrentPeerListener.ET_ANNOUNCE, null ); if ( reply != null ){ List limited_peers = (List)reply.get( "limited_peers" ); if ( limited_peers != null ){ if ( explicit_limited_peers == null ){ explicit_limited_peers = new ArrayList<TRTrackerServerSimplePeer>(); } for (int j=0;j<limited_peers.size();j++){ Map peer_map = (Map)limited_peers.get(j); String ip = (String)peer_map.get("ip"); int port = ((Long)peer_map.get( "port")).intValue(); String reuse_key = ip + ":" + port; TRTrackerServerPeerImpl peer = (TRTrackerServerPeerImpl)peer_reuse_map.get( reuse_key ); if ( peer != null && !explicit_limited_peers.contains( peer )){ explicit_limited_peers.add( peer ); } } } List biased_peers = (List)reply.get( "biased_peers" ); if ( biased_peers != null ){ if ( explicit_biased_peers == null ){ explicit_biased_peers = new ArrayList<TRTrackerServerSimplePeer>(); } for (int j=0;j<biased_peers.size();j++){ Map peer_map = (Map)biased_peers.get(j); String ip = (String)peer_map.get("ip"); int port = ((Long)peer_map.get( "port")).intValue(); String reuse_key = ip + ":" + port; TRTrackerServerSimplePeer peer = peer_reuse_map.get( reuse_key ); if ( peer == null ){ peer = new temporaryBiasedSeed( ip, port ); } if ( !explicit_biased_peers.contains( peer )){ explicit_biased_peers.add( peer ); } } } remove_ips = (Set)reply.get( "remove_ips" ); } }catch( Throwable e ){ Debug.printStackTrace(e); } } } } boolean requester_is_biased; if ( requesting_peer == null ){ Set bp = server.getBiasedPeers(); if ( bp == null ){ requester_is_biased = false; }else{ requester_is_biased = bp.contains( ip_address ); } }else{ requester_is_biased = requesting_peer.isBiased(); } if ( caching_enabled && explicit_limited_peers == null && explicit_biased_peers == null && !requester_is_biased && remove_ips == null && (!nat_warning) && preprocess_map.size() == 0 && // don't cache if we've got pre-process stuff to add cache_millis > 0 && num_want >= MIN_CACHE_ENTRY_SIZE && total_peers >= TRTrackerServerImpl.getAnnounceCachePeerThreshold() && crypto_level != TRTrackerServerPeer.CRYPTO_REQUIRED ){ // no cache for crypto required peers // too busy to bother with network position stuff network_position = null; // note that we've got to select a cache entry that is somewhat // relevant to the num_want param (but NOT greater than it) // remove stuff that's too old Iterator it = announce_cache.keySet().iterator(); while( it.hasNext() ){ Integer key = (Integer)it.next(); announceCacheEntry entry = (announceCacheEntry)announce_cache.get( key ); if ( now - entry.getTime() > cache_millis ){ it.remove(); } } // look for an entry with a reasonable num_want // e.g. for 100 look between 50 and 100 for (int i=num_want/10;i>num_want/20;i--){ announceCacheEntry entry = (announceCacheEntry)announce_cache.get(new Integer(i)); if( entry != null ){ if ( now - entry.getTime() > cache_millis ){ announce_cache.remove( new Integer(i)); }else{ // make sure this is compatible if ( entry.getSendPeerIds() == send_peer_ids && entry.getCompactMode() == compact_mode ){ return( entry.getData()); } } } } add_to_cache = true; } LinkedList rep_peers = new LinkedList(); // System.out.println( "exportPeersToMap: num_want = " + num_want + ", max = " + max_peers ); // if they want them all simply give them the set if ( num_want > 0 && explicit_limited_peers == null ){ if ( num_want >= total_peers){ // if they want them all simply give them the set for (int i=0;i<peer_list.size();i++){ TRTrackerServerPeerImpl peer = (TRTrackerServerPeerImpl)peer_list.get(i); if ( peer == null || peer == requesting_peer ){ }else if ( now > peer.getTimeout()){ // System.out.println( "removing timed out client '" + peer.getString()); removePeer( peer, i, TRTrackerServerTorrentPeerListener.ET_TIMEOUT, null ); }else if ( peer.getTCPPort() == 0 ){ // a port of 0 means that the peer definitely can't accept incoming connections }else if ( crypto_level == TRTrackerServerPeer.CRYPTO_NONE && peer.getCryptoLevel() == TRTrackerServerPeer.CRYPTO_REQUIRED ){ // don't return "crypto required" peers to those that can't correctly connect to them /* change this to make the explicit ones additional, not replacing }else if ( explicit_biased_peers != null && peer.isBiased()){ */ // if we have an explicit biased peer list and this peer is biased // skip here as we add them later }else if ( remove_ips != null && remove_ips.contains( new String( peer.getIP()))){ // skippy skippy }else if ( include_seeds || !peer.isSeed()){ Map rep_peer = new HashMap(3); if ( send_peer_ids ){ rep_peer.put( "peer id", peer.getPeerId().getHash()); } if ( compact_mode != COMPACT_MODE_NONE ){ byte[] peer_bytes = peer.getIPAddressBytes(); if ( peer_bytes == null ){ continue; } rep_peer.put( "ip", peer_bytes ); if ( compact_mode >= COMPACT_MODE_AZ ){ rep_peer.put( "azver", new Long( peer.getAZVer())); rep_peer.put( "azudp", new Long( peer.getUDPPort())); if ( peer.isSeed()){ rep_peer.put( "azhttp", new Long( peer.getHTTPPort())); } if ( compact_mode >= COMPACT_MODE_XML ){ rep_peer.put( "ip", peer.getIPAsRead() ); }else{ rep_peer.put( "azup", new Long( peer.getUpSpeed())); if ( peer.isBiased()){ rep_peer.put( "azbiased", "" ); } if ( network_position != null ){ DHTNetworkPosition peer_pos = peer.getNetworkPosition(); if ( peer_pos != null && network_position.getPositionType() == peer_pos.getPositionType()){ rep_peer.put( "azrtt", new Long( (long)peer_pos.estimateRTT(network_position ))); } } } } }else{ rep_peer.put( "ip", peer.getIPAsRead() ); } rep_peer.put( "port", new Long( peer.getTCPPort())); if ( crypto_level != TRTrackerServerPeer.CRYPTO_NONE ){ rep_peer.put( "crypto_flag", new Long( peer.getCryptoLevel() == TRTrackerServerPeer.CRYPTO_REQUIRED?1:0)); } if ( peer.isBiased()){ rep_peers.addFirst( rep_peer ); }else{ rep_peers.addLast( rep_peer ); } } } }else{ int peer_list_size = peer_list.size(); // to avoid returning duplicates when doing the two-loop check // for nat selection we maintain an array of markers if ( duplicate_peer_checker.length < peer_list_size ){ duplicate_peer_checker = new byte[peer_list_size*2]; duplicate_peer_checker_index = 1; }else if ( duplicate_peer_checker.length > (peer_list_size*2)){ duplicate_peer_checker = new byte[(3*peer_list_size)/2]; duplicate_peer_checker_index = 1; }else{ duplicate_peer_checker_index++; if ( duplicate_peer_checker_index == 0 ){ Arrays.fill( duplicate_peer_checker, (byte)0); duplicate_peer_checker_index = 1; } } boolean peer_removed = false; try{ // got to suspend peer list compaction as we rely on the // list staying the same size during processing below peer_list_compaction_suspended = true; // too costly to randomise as below. use more efficient but slightly less accurate // approach // two pass process if bad nat detection enabled int added = 0; //int bad_nat_added = 0; for (int bad_nat_loop=TRTrackerServerNATChecker.getSingleton().isEnabled()?0:1;bad_nat_loop<2;bad_nat_loop++){ int limit = num_want*2; // some entries we find might not be usable // so in the limit search for more if ( num_want*3 > total_peers ){ limit++; } int biased_peers_count = 0; if ( biased_peers != null ){ // explicit are additional && explicit_biased_peers == null ){ if ( biased_peers.size() > 1 ){ // juggle things a bit Object x = biased_peers.remove(0); biased_peers.add( random.nextInt( biased_peers.size() + 1 ), x); } biased_peers_count = Math.min( min_biased_peers, biased_peers.size()); } for (int i=0;i<limit && added < num_want;i++){ int peer_index; TRTrackerServerPeerImpl peer; // deal with bias up front if ( bad_nat_loop == 1 && i < biased_peers_count ){ peer = (TRTrackerServerPeerImpl)biased_peers.get(i); peer_index = -1; // don't know actual index and don't need to as biased peers processed separately }else{ peer_index = random.nextInt(peer_list_size); peer = (TRTrackerServerPeerImpl)peer_list.get(peer_index); if ( peer == null || peer.isBiased()){ continue; } } if ( now > peer.getTimeout()){ removePeer( peer, TRTrackerServerTorrentPeerListener.ET_TIMEOUT, null ); peer_removed = true; }else if ( requesting_peer == peer || peer.getTCPPort() == 0 ){ // a port of 0 means that the peer definitely can't accept incoming connections }else if ( crypto_level == TRTrackerServerPeer.CRYPTO_NONE && peer.getCryptoLevel() == TRTrackerServerPeer.CRYPTO_REQUIRED ){ // don't return "crypto required" peers to those that can't correctly connect to them }else if ( remove_ips != null && remove_ips.contains( new String( peer.getIP()))){ // skippy skippy }else if ( include_seeds || !peer.isSeed()){ boolean bad_nat = peer.isNATStatusBad(); if ( ( bad_nat_loop == 0 && !bad_nat ) || ( bad_nat_loop == 1 )){ if ( peer_index == -1 || duplicate_peer_checker[peer_index] != duplicate_peer_checker_index ){ if ( peer_index != -1 ){ duplicate_peer_checker[peer_index] = duplicate_peer_checker_index; } //if ( bad_nat ){ // // bad_nat_added++; //} added++; Map rep_peer = new HashMap(3); if ( send_peer_ids ){ rep_peer.put( "peer id", peer.getPeerId().getHash()); } if ( compact_mode != COMPACT_MODE_NONE ){ byte[] peer_bytes = peer.getIPAddressBytes(); if ( peer_bytes == null ){ continue; } rep_peer.put( "ip", peer_bytes ); if ( compact_mode >= COMPACT_MODE_AZ ){ rep_peer.put( "azver", new Long( peer.getAZVer())); rep_peer.put( "azudp", new Long( peer.getUDPPort())); if ( peer.isSeed()){ rep_peer.put( "azhttp", new Long( peer.getHTTPPort())); } if ( compact_mode >= COMPACT_MODE_XML ){ rep_peer.put( "ip", peer.getIPAsRead() ); }else{ rep_peer.put( "azup", new Long( peer.getUpSpeed())); if ( peer.isBiased()){ rep_peer.put( "azbiased", "" ); } if ( network_position != null ){ DHTNetworkPosition peer_pos = peer.getNetworkPosition(); if ( peer_pos != null && network_position.getPositionType() == peer_pos.getPositionType()){ rep_peer.put( "azrtt", new Long( (long)peer_pos.estimateRTT(network_position ))); } } } } }else{ rep_peer.put( "ip", peer.getIPAsRead() ); } rep_peer.put( "port", new Long( peer.getTCPPort())); if ( crypto_level != TRTrackerServerPeer.CRYPTO_NONE ){ rep_peer.put( "crypto_flag", new Long( peer.getCryptoLevel() == TRTrackerServerPeer.CRYPTO_REQUIRED?1:0)); } if ( peer.isBiased()){ rep_peers.addFirst( rep_peer ); }else{ rep_peers.addLast( rep_peer ); } } } } } } // System.out.println( "num_want = " + num_want + ", added = " + added + ", bad_nat = " + bad_nat_added ); }finally{ peer_list_compaction_suspended = false; if ( peer_removed ){ checkForPeerListCompaction( false ); } } /* }else{ // given up on this approach for the moment as too costly // randomly select the peers to return LinkedList peers = new LinkedList( peer_map.keySet()); int added = 0; while( added < num_want && peers.size() > 0 ){ String key = (String)peers.remove(random.nextInt(peers.size())); TRTrackerServerPeerImpl peer = (TRTrackerServerPeerImpl)peer_map.get(key); if ( now > peer.getTimeout()){ removePeer( peer, TRTrackerServerTorrentPeerListener.ET_TIMEOUT, null ); }else if ( peer.getTCPPort() == 0 ){ // a port of 0 means that the peer definitely can't accept incoming connections }else if ( crypto_level == TRTrackerServerPeer.CRYPTO_NONE && peer.getCryptoLevel() == TRTrackerServerPeer.CRYPTO_REQUIRED ){ // don't return "crypto required" peers to those that can't correctly connect to them }else if ( include_seeds || !peer.isSeed()){ added++; Map rep_peer = new HashMap(3); // don't use TreeMap as default is "compact" // so we never actually encode anyway if ( send_peer_ids ){ rep_peer.put( "peer id", peer.getPeerId().getHash()); } if ( compact_mode != COMPACT_MODE_NONE ){ byte[] peer_bytes = peer.getIPBytes(); if ( peer_bytes == null ){ continue; } rep_peer.put( "ip", peer_bytes ); if ( compact_mode >= COMPACT_MODE_AZ ){ rep_peer.put( "azver", new Long( peer.getAZVer())); rep_peer.put( "azudp", new Long( peer.getUDPPort())); if ( peer.isSeed()){ rep_peer.put( "azhttp", new Long( peer.getHTTPPort())); } } }else{ rep_peer.put( "ip", peer.getIPAsRead() ); } rep_peer.put( "port", new Long( peer.getTCPPort())); if ( crypto_level != TRTrackerServerPeer.CRYPTO_NONE ){ rep_peer.put( "crypto_flag", new Long( peer.getCryptoLevel() == TRTrackerServerPeer.CRYPTO_REQUIRED?1:0)); } rep_peers.add( rep_peer ); } }*/ } } if ( include_seeds && explicit_limited_peers == null && !send_peer_ids && seed_count < 3 && queued_peers != null ){ Iterator it = queued_peers.iterator(); List added = new ArrayList( QUEUED_PEERS_ADD_MAX ); while( it.hasNext() && num_want > rep_peers.size() && added.size() < QUEUED_PEERS_ADD_MAX ){ QueuedPeer peer = (QueuedPeer)it.next(); if ( peer.isTimedOut( now )){ it.remove(); }else if ( crypto_level == TRTrackerServerPeer.CRYPTO_NONE && peer.getCryptoLevel() == TRTrackerServerPeer.CRYPTO_REQUIRED ){ // don't return "crypto required" peers to those that can't correctly connect to them }else if ( remove_ips != null && remove_ips.contains( peer.getIP())){ // skippy skippy }else{ Map rep_peer = new HashMap(3); if ( compact_mode != COMPACT_MODE_NONE ){ byte[] peer_bytes = peer.getIPAddressBytes(); if ( peer_bytes == null ){ continue; } rep_peer.put( "ip", peer_bytes ); if ( compact_mode >= COMPACT_MODE_AZ ){ rep_peer.put( "azver", new Long( peer.getAZVer())); rep_peer.put( "azudp", new Long( peer.getUDPPort())); if ( peer.isSeed()){ rep_peer.put( "azhttp", new Long( peer.getHTTPPort())); } if ( compact_mode >= COMPACT_MODE_XML ){ rep_peer.put( "ip", peer.getIPAsRead()); } } }else{ rep_peer.put( "ip", peer.getIPAsRead()); } rep_peer.put( "port", new Long( peer.getTCPPort())); if ( crypto_level != TRTrackerServerPeer.CRYPTO_NONE ){ rep_peer.put( "crypto_flag", new Long( peer.getCryptoLevel() == TRTrackerServerPeer.CRYPTO_REQUIRED?1:0)); } // System.out.println( "added queued peer " + peer.getString()); rep_peers.addLast( rep_peer ); added.add( peer ); // it'll be added back in below, don't worry! it.remove(); } } for (int i=0;i<added.size();i++){ queued_peers.add( added.get(i)); } } Map root = new TreeMap(); // user TreeMap to pre-sort so encoding quicker if ( preprocess_map.size() > 0 ){ root.putAll( preprocess_map ); } if ( explicit_limited_peers != null ){ for (int i=0;i<explicit_limited_peers.size();i++){ num_want--; TRTrackerServerSimplePeer peer = explicit_limited_peers.get(i); exportPeer(rep_peers, peer, send_peer_ids, compact_mode, crypto_level, network_position); } } if ( explicit_biased_peers != null ){ for (int i=0;i<explicit_biased_peers.size();i++){ num_want--; TRTrackerServerSimplePeer peer = explicit_biased_peers.get(i); exportPeer(rep_peers, peer, send_peer_ids, compact_mode, crypto_level, network_position); } } if ( explicit_manual_biased_peers != null ){ if ( requesting_peer != null && !requesting_peer.isSeed()){ Object[] explicit_peer = (Object[])explicit_manual_biased_peers.get( explicit_next_peer++ ); if ( explicit_next_peer == explicit_manual_biased_peers.size()){ explicit_next_peer = 0; } Map rep_peer = new HashMap(3); if ( send_peer_ids ){ byte[] peer_id = new byte[20]; random.nextBytes( peer_id ); rep_peer.put( "peer id", peer_id ); } if ( compact_mode != COMPACT_MODE_NONE ){ byte[] peer_bytes = (byte[])explicit_peer[1]; rep_peer.put( "ip", peer_bytes ); if ( compact_mode >= COMPACT_MODE_AZ ){ rep_peer.put( "azver", new Long( 0 )); // non-az rep_peer.put( "azudp", new Long( 0 )); rep_peer.put( "azup", new Long( 0 )); rep_peer.put( "azbiased", "" ); } }else{ rep_peer.put( "ip", ((String)explicit_peer[0]).getBytes()); } rep_peer.put( "port", new Long( ((Integer)explicit_peer[2]).intValue())); if ( crypto_level != TRTrackerServerPeer.CRYPTO_NONE ){ rep_peer.put( "crypto_flag", new Long( 0 )); } rep_peers.addFirst( rep_peer ); } } int num_peers_returned = rep_peers.size(); Iterator it = rep_peers.iterator(); if ( compact_mode == COMPACT_MODE_AZ ){ byte[] compact_peers = new byte[num_peers_returned*9]; int index = 0; while( it.hasNext()){ Map rep_peer = (Map)it.next(); byte[] ip = (byte[])rep_peer.get( "ip" ); int tcp_port = ((Long)rep_peer.get( "port" )).intValue(); int udp_port = ((Long)rep_peer.get( "azudp" )).intValue(); Long crypto_flag_l = (Long)rep_peer.get( "crypto_flag" ); byte crypto_flag = crypto_flag_l==null?0:crypto_flag_l.byteValue(); int pos = index*9; System.arraycopy( ip, 0, compact_peers, pos, 4 ); pos += 4; compact_peers[pos++] = (byte)(tcp_port>>8); compact_peers[pos++] = (byte)(tcp_port&0xff); compact_peers[pos++] = (byte)(udp_port>>8); compact_peers[pos++] = (byte)(udp_port&0xff); compact_peers[pos++] = crypto_flag; index++; } root.put( "peers", compact_peers ); root.put( "azcompact", new Long(1)); }else if ( compact_mode == COMPACT_MODE_AZ_2 ){ List compact_peers = new ArrayList( num_peers_returned ); while( it.hasNext()){ Map rep_peer = (Map)it.next(); Map peer = new HashMap(); compact_peers.add( peer ); byte[] ip = (byte[])rep_peer.get( "ip" ); peer.put( "i", ip ); int tcp_port = ((Long)rep_peer.get( "port" )).intValue(); peer.put( "t", new byte[]{ (byte)(tcp_port>>8), (byte)(tcp_port&0xff) }); int udp_port = ((Long)rep_peer.get( "azudp" )).intValue(); if ( udp_port != 0 ){ if ( udp_port == tcp_port ){ peer.put( "u", new byte[0] ); }else{ peer.put( "u", new byte[]{ (byte)(udp_port>>8), (byte)(udp_port&0xff) }); } } Long http_port_l = (Long)rep_peer.get( "azhttp" ); if ( http_port_l != null ){ int http_port = http_port_l.intValue(); if ( http_port != 0 ){ peer.put( "h", new byte[]{ (byte)(http_port>>8), (byte)(http_port&0xff) }); } } Long crypto_flag_l = (Long)rep_peer.get( "crypto_flag" ); byte crypto_flag = crypto_flag_l==null?0:crypto_flag_l.byteValue(); if ( crypto_flag != 0 ){ peer.put( "c", new byte[]{ crypto_flag } ); } Long az_ver_l = (Long)rep_peer.get( "azver" ); byte az_ver = az_ver_l==null?0:az_ver_l.byteValue(); if ( az_ver != 0 ){ peer.put( "v", new Long(az_ver)); } Long up_speed = (Long)rep_peer.get( "azup" ); if ( up_speed != null && up_speed.longValue() != 0 ){ peer.put( "s", up_speed ); } Long rtt = (Long)rep_peer.get( "azrtt" ); if ( rtt != null ){ peer.put( "r", rtt ); } if ( rep_peer.containsKey("azbiased")){ peer.put( "b", new Long(1)); } } root.put( "peers", compact_peers ); root.put( "azcompact", new Long(2)); }else if ( compact_mode == COMPACT_MODE_XML ){ List xml_peers = new ArrayList( num_peers_returned ); while( it.hasNext()){ Map rep_peer = (Map)it.next(); Map peer = new HashMap(); xml_peers.add( peer ); peer.put( "ip", rep_peer.get( "ip" ) ); peer.put( "tcp", rep_peer.get( "port" )); int udp_port = ((Long)rep_peer.get( "azudp" )).intValue(); if ( udp_port != 0 ){ peer.put( "udp", new Long( udp_port )); } Long http_port_l = (Long)rep_peer.get( "azhttp" ); if ( http_port_l != null ){ int http_port = http_port_l.intValue(); if ( http_port != 0 ){ peer.put( "http", new Long( http_port )); } } } root.put( "peers", xml_peers ); }else{ byte[] crypto_flags = null; if ( crypto_level != TRTrackerServerPeer.CRYPTO_NONE ){ crypto_flags = new byte[num_peers_returned]; } if ( compact_mode == COMPACT_MODE_NORMAL ){ byte[] compact_peers = new byte[num_peers_returned*6]; int index = 0; int num_ipv4 = 0; int num_ipv6 = 0; while( it.hasNext()){ Map rep_peer = (Map)it.next(); byte[] ip = (byte[])rep_peer.get( "ip" ); if ( ip.length > 4 ){ num_ipv6++; // continue and fill in crypto return }else{ num_ipv4++; if ( num_ipv6 == 0 ){ int port = ((Long)rep_peer.get( "port" )).intValue(); int pos = index*6; System.arraycopy( ip, 0, compact_peers, pos, 4 ); pos += 4; compact_peers[pos++] = (byte)(port>>8); compact_peers[pos++] = (byte)(port&0xff); } } if ( crypto_flags != null ){ Long crypto_flag = (Long)rep_peer.remove( "crypto_flag" ); crypto_flags[index] = crypto_flag.byteValue(); } index++; } // inefficient hack to support ipv6 compact for the moment if ( num_ipv6 > 0 ){ byte[] compact_peers_v4 = new byte[num_ipv4*6]; byte[] compact_peers_v6 = new byte[num_ipv6*18]; it = rep_peers.iterator(); int v4_index = 0; int v6_index = 0; while( it.hasNext()){ Map rep_peer = (Map)it.next(); byte[] ip = (byte[])rep_peer.get( "ip" ); int port = ((Long)rep_peer.get( "port" )).intValue(); if ( ip.length > 4 ){ int pos = v6_index*18; System.arraycopy( ip, 0, compact_peers_v6, pos, 16 ); pos += 16; compact_peers_v6[pos++] = (byte)(port>>8); compact_peers_v6[pos++] = (byte)(port&0xff); v6_index++; }else{ int pos = v4_index*6; System.arraycopy( ip, 0, compact_peers_v4, pos, 4 ); pos += 4; compact_peers_v4[pos++] = (byte)(port>>8); compact_peers_v4[pos++] = (byte)(port&0xff); v4_index++; } } if ( compact_peers_v4.length > 0 ){ root.put( "peers", compact_peers_v4 ); } if ( compact_peers_v6.length > 0 ){ root.put( "peers6", compact_peers_v6 ); } }else{ root.put( "peers", compact_peers ); } }else{ int index = 0; while( it.hasNext()){ Map rep_peer = (Map)it.next(); if ( crypto_flags != null ){ Long crypto_flag = (Long)rep_peer.remove( "crypto_flag" ); crypto_flags[index] = crypto_flag.byteValue(); } index++; } root.put( "peers", rep_peers ); } if ( crypto_flags != null ){ root.put( "crypto_flags", crypto_flags ); } } root.put( "interval", new Long( interval )); root.put( "min interval", new Long( min_interval )); if ( nat_warning ){ requesting_peer.setNATStatus( TRTrackerServerPeerImpl.NAT_CHECK_FAILED_AND_REPORTED ); root.put( "warning message", ("Unable to connect to your incoming data port (" + requesting_peer.getIP() + ":" + requesting_peer.getTCPPort() +"). " + "This will result in slow downloads. Please check your firewall/router settings").getBytes()); } // also include scrape details root.put( "complete", new Long( getSeedCountForScrape( requester_is_biased ))); root.put( "incomplete", new Long( getLeecherCount() )); root.put( "downloaded", new Long(stats.getCompletedCount())); if ( add_to_cache ){ announce_cache.put( new Integer((num_peers_returned+9)/10), new announceCacheEntry( root, send_peer_ids, compact_mode )); } return( root ); }finally{ this_mon.exit(); } } private void exportPeer( LinkedList rep_peers, TRTrackerServerSimplePeer peer, boolean send_peer_ids, byte compact_mode, byte crypto_level, DHTNetworkPosition network_position ) { Map rep_peer = new HashMap(3); if ( send_peer_ids ){ rep_peer.put( "peer id", peer.getPeerId().getHash()); } if ( compact_mode != COMPACT_MODE_NONE ){ byte[] peer_bytes = peer.getIPAddressBytes(); if ( peer_bytes == null ){ return; } rep_peer.put( "ip", peer_bytes ); if ( compact_mode >= COMPACT_MODE_AZ ){ rep_peer.put( "azver", new Long( peer.getAZVer())); rep_peer.put( "azudp", new Long( peer.getUDPPort())); if ( peer.isSeed()){ rep_peer.put( "azhttp", new Long( peer.getHTTPPort())); } if ( compact_mode >= COMPACT_MODE_XML ){ rep_peer.put( "ip", peer.getIPAsRead() ); }else{ rep_peer.put( "azup", new Long( peer.getUpSpeed())); if ( peer.isBiased()){ rep_peer.put( "azbiased", "" ); } if ( network_position != null ){ DHTNetworkPosition peer_pos = peer.getNetworkPosition(); if ( peer_pos != null && network_position.getPositionType() == peer_pos.getPositionType()){ rep_peer.put( "azrtt", new Long( (long)peer_pos.estimateRTT(network_position ))); } } } } }else{ rep_peer.put( "ip", peer.getIPAsRead() ); } rep_peer.put( "port", new Long( peer.getTCPPort())); if ( crypto_level != TRTrackerServerPeer.CRYPTO_NONE ){ rep_peer.put( "crypto_flag", new Long( peer.getCryptoLevel() == TRTrackerServerPeer.CRYPTO_REQUIRED?1:0)); } if ( peer.isBiased()){ rep_peers.addFirst( rep_peer ); }else{ rep_peers.addLast( rep_peer ); } } public Map exportScrapeToMap( String url_parameters, String ip_address, boolean allow_cache ) throws TRTrackerServerException { try{ this_mon.enter(); handleRedirects( url_parameters, ip_address, true ); stats.addScrape(); long now = SystemTime.getCurrentTime(); long diff = now - last_scrape_calc_time; if( allow_cache && last_scrape != null && diff < TRTrackerServerImpl.getScrapeCachePeriod() && !(diff < 0) ){ return( last_scrape ); } last_scrape = new TreeMap(); last_scrape_calc_time = now; boolean requester_is_biased; Set bp = server.getBiasedPeers(); if ( bp == null ){ requester_is_biased = false; }else{ requester_is_biased = bp.contains( ip_address ); } last_scrape.put( "complete", new Long( getSeedCountForScrape( requester_is_biased ))); last_scrape.put( "incomplete", new Long( getLeecherCount())); last_scrape.put( "downloaded", new Long(stats.getCompletedCount())); return( last_scrape ); }finally{ this_mon.exit(); } } protected void checkTimeouts() { try{ this_mon.enter(); long now = SystemTime.getCurrentTime(); int new_bad_NAT_count = 0; // recalc seed count as this drifts for some reason (maybe seeds switching back to leechers // on recheck fail, not sure) int new_seed_count = 0; try{ peer_list_compaction_suspended = true; for (int i=0;i<peer_list.size();i++){ TRTrackerServerPeerImpl peer = (TRTrackerServerPeerImpl)peer_list.get(i); if ( peer == null ){ continue; } if ( now > peer.getTimeout()){ removePeer( peer, i, TRTrackerServerTorrentPeerListener.ET_TIMEOUT, null ); }else{ if ( peer.isSeed()){ new_seed_count++; } if ( peer.isNATStatusBad()){ new_bad_NAT_count++; } } } }finally{ peer_list_compaction_suspended = false; } bad_NAT_count = new_bad_NAT_count; seed_count = new_seed_count; if ( removed_count > 1000 ){ removed_count = 0; checkForPeerListCompaction( true ); // rehash HashMap new_peer_map = new HashMap(peer_map); HashMap new_peer_reuse_map = new HashMap(peer_reuse_map); peer_map = new_peer_map; peer_reuse_map = new_peer_reuse_map; }else{ checkForPeerListCompaction( false ); } Iterator it = lightweight_seed_map.values().iterator(); while( it.hasNext()){ lightweightSeed lws = (lightweightSeed)it.next(); if ( now > lws.getTimeout()){ it.remove(); } } }finally{ this_mon.exit(); } } protected void checkForPeerListCompaction( boolean force ) { if ( peer_list_hole_count > 0 && !peer_list_compaction_suspended ){ if ( force || peer_list_hole_count > peer_map.size()/10 ){ ArrayList new_peer_list = new ArrayList( peer_list.size() - (peer_list_hole_count/2)); int holes_found = 0; for (int i=0;i<peer_list.size();i++){ Object obj = peer_list.get(i); if ( obj == null ){ holes_found++; }else{ new_peer_list.add( obj ); } } if( holes_found != peer_list_hole_count ){ Debug.out( "TRTrackerTorrent:compactHoles: count mismatch" ); } peer_list = new_peer_list; peer_list_hole_count = 0; } } } protected void updateXferStats( int bytes_in, int bytes_out ) { stats.addXferStats( bytes_in, bytes_out ); } public TRTrackerServerTorrentStats getStats() { return( stats ); } protected int getPeerCount() { return( peer_map.size() + lightweight_seed_map.size()); } protected int getSeedCount() { if ( seed_count < 0 ){ Debug.out( "seed count negative" ); } return( seed_count + lightweight_seed_map.size()); } protected int getSeedCountForScrape( boolean requester_is_biased ) { int seeds = getSeedCount(); if ( biased_peers != null && !requester_is_biased ){ int bpc = 0; Iterator it = biased_peers.iterator(); while( it.hasNext()){ TRTrackerServerPeerImpl bp = (TRTrackerServerPeerImpl)it.next(); if ( bp.isSeed()){ seeds--; bpc++; } } if ( seeds < 0 ){ seeds = 0; } // retain at least one biased seed if ( bpc > 0 ){ seeds++; } } // if we have any queued then lets add at least one in to indicate potential int queued = getQueuedCount(); if ( queued > 0 ){ seeds++; } return( seeds ); } protected int getLeecherCount() { // this isn't synchronised so could possible end up negative int res = peer_map.size() - seed_count; return( res<0?0:res ); } public TRTrackerServerPeer[] getPeers() { try{ this_mon.enter(); TRTrackerServerPeer[] res = new TRTrackerServerPeer[peer_map.size()]; peer_map.values().toArray( res ); return( res ); }finally{ this_mon.exit(); } } protected int getQueuedCount() { List l = queued_peers; if ( l == null ){ return( 0 ); } return( l.size()); } public TRTrackerServerPeerBase[] getQueuedPeers() { try{ this_mon.enter(); if ( queued_peers == null ){ return( new TRTrackerServerPeerBase[0] ); } TRTrackerServerPeerBase[] res = new TRTrackerServerPeerBase[queued_peers.size()]; queued_peers.toArray( res ); return( res ); }finally{ this_mon.exit(); } } public HashWrapper getHash() { return( hash ); } public void addExplicitBiasedPeer( String ip, int port ) { byte[] bytes = HostNameToIPResolver.hostAddressToBytes( ip ); if ( bytes != null ){ try{ this_mon.enter(); if ( explicit_manual_biased_peers == null ){ explicit_manual_biased_peers = new ArrayList(); } explicit_manual_biased_peers.add( new Object[]{ ip, bytes, new Integer( port )}); }finally{ this_mon.exit(); } } } public void setRedirects( URL[] urls ) { try{ this_mon.enter(); redirects = urls; }finally{ this_mon.exit(); } } public URL[] getRedirects() { return( redirects ); } protected void handleRedirects( String url_parameters, String real_ip_address, boolean scrape ) throws TRTrackerServerException { if ( redirects != null ){ if ( url_parameters.indexOf("permredirect") != -1 ){ Debug.out( "redirect recursion" ); throw( new TRTrackerServerException( "redirection recursion not supported" )); } URL redirect = redirects[(real_ip_address.hashCode()&0x7fffffff)%redirects.length]; Map headers = new HashMap(); String redirect_str = redirect.toString(); if ( scrape ){ int pos = redirect_str.indexOf( "/announce" ); if ( pos == -1 ){ return; } redirect_str = redirect_str.substring( 0, pos ) + "/scrape" + redirect_str.substring( pos + 9 ); } if ( redirect_str.indexOf('?' ) == -1 ){ redirect_str += "?"; }else{ redirect_str += "&"; } redirect_str += "permredirect=1"; if ( url_parameters.length() > 0 ){ redirect_str += "&" + url_parameters; } System.out.println( "redirect -> " + redirect_str ); headers.put( "Location", redirect_str); throw( new TRTrackerServerException(301, "Moved Permanently", headers )); } } public void addListener( TRTrackerServerTorrentListener l ) { listeners.add(l); if ( deleted ){ l.deleted(this); } } public void removeListener( TRTrackerServerTorrentListener l ) { listeners.remove(l); } protected void peerEvent( TRTrackerServerPeer peer, int event, String url_parameters ) throws TRTrackerServerException { if ( peer_listeners != null ){ for (int i=0;i<peer_listeners.size();i++){ try{ ((TRTrackerServerTorrentPeerListener)peer_listeners.get(i)).eventOccurred( this, peer, event, url_parameters ); }catch( TRTrackerServerException e ){ throw( e ); }catch( Throwable e ){ Debug.printStackTrace(e); } } } } public void addPeerListener( TRTrackerServerTorrentPeerListener l ) { if ( peer_listeners == null ){ peer_listeners = new ArrayList(); } peer_listeners.add( l ); } public void removePeerListener( TRTrackerServerTorrentPeerListener l ) { if ( peer_listeners != null ){ peer_listeners.remove(l); } } public void disableCaching() { caching_enabled = false; } public boolean isCachingEnabled() { return( caching_enabled ); } public int getBadNATPeerCount() { return( bad_NAT_count ); } protected void delete() { deleted = true; for (int i=0;i<listeners.size();i++){ ((TRTrackerServerTorrentListener)listeners.get(i)).deleted(this); } } static class announceCacheEntry { protected Map data; protected boolean send_peer_ids; protected byte compact_mode; protected long time; protected announceCacheEntry( Map _data, boolean _send_peer_ids, byte _compact_mode ) { data = _data; send_peer_ids = _send_peer_ids; compact_mode = _compact_mode; time = SystemTime.getCurrentTime(); } protected boolean getSendPeerIds() { return( send_peer_ids ); } protected byte getCompactMode() { return( compact_mode ); } protected long getTime() { return( time ); } protected Map getData() { return( data ); } } protected static class lightweightSeed { long timeout; long last_contact_time; long uploaded; byte nat_status; protected lightweightSeed( long _now, long _timeout, long _uploaded, byte _nat_status ) { last_contact_time = _now; timeout = _timeout; uploaded = _uploaded; nat_status = _nat_status; } protected long getTimeout() { return( timeout ); } protected long getLastContactTime() { return( last_contact_time ); } protected long getUploaded() { return( uploaded ); } protected byte getNATStatus() { return( nat_status ); } } protected static class QueuedPeer implements TRTrackerServerPeerBase { private static final byte FLAG_SEED = 0x01; private static final byte FLAG_BIASED = 0x02; private short tcp_port; private short udp_port; private short http_port; private byte[] ip; private byte crypto_level; private byte az_ver; private int create_time_secs; private int timeout_secs; private byte flags; protected QueuedPeer( String _ip_str, int _tcp_port, int _udp_port, int _http_port, byte _crypto_level, byte _az_ver, int _timeout_secs, boolean _seed, boolean _biased ) { try{ ip = _ip_str.getBytes( Constants.BYTE_ENCODING ); }catch( UnsupportedEncodingException e ){ Debug.printStackTrace(e); } tcp_port = (short)_tcp_port; udp_port = (short)_udp_port; http_port = (short)_http_port; crypto_level = _crypto_level; az_ver = _az_ver; setFlag( FLAG_SEED, _seed ); setFlag( FLAG_BIASED, _biased ); create_time_secs = (int)( SystemTime.getCurrentTime()/1000 ); timeout_secs = _timeout_secs * TRTrackerServerImpl.CLIENT_TIMEOUT_MULTIPLIER; } protected boolean sameAs( TRTrackerServerPeerImpl peer ) { return( tcp_port == peer.getTCPPort() && Arrays.equals( ip, peer.getIPAsRead()) && isIPOverride() == peer.isIPOverride()); } protected boolean sameAs( QueuedPeer other ) { return( tcp_port == other.tcp_port && Arrays.equals( ip,other.ip )); } protected byte[] getIPAsRead() { return( ip ); } public String getIP() { try{ return( new String( ip, Constants.BYTE_ENCODING )); }catch( UnsupportedEncodingException e ){ return( new String( ip )); } } protected boolean isSeed() { return( getFlag( FLAG_SEED )); } protected void setBiased( boolean _biased ) { setFlag( FLAG_BIASED, _biased ); } protected boolean isBiased() { return( getFlag( FLAG_BIASED )); } protected boolean isIPOverride() { // we never allow IP override queued peers return( false ); } protected void setFlag( byte flag, boolean value ) { if ( value ){ flags |= flag; }else{ flags &= ~flag; } } protected boolean getFlag( byte flag ) { return((flags & flag ) != 0 ); } protected byte[] getIPAddressBytes() { try{ return( HostNameToIPResolver.hostAddressToBytes( new String( ip, Constants.BYTE_ENCODING ))); }catch( UnsupportedEncodingException e ){ Debug.printStackTrace(e); return( null ); } } public int getTCPPort() { return( tcp_port & 0xffff ); } public int getUDPPort() { return( udp_port & 0xffff ); } public int getHTTPPort() { return( http_port & 0xffff ); } protected byte getCryptoLevel() { return( crypto_level ); } protected byte getAZVer() { return( az_ver ); } protected int getCreateTime() { return( create_time_secs ); } protected boolean isTimedOut( long now_millis ) { int now_secs = (int)(now_millis/1000); if ( now_secs < create_time_secs ){ create_time_secs = now_secs; } return( create_time_secs + timeout_secs < now_secs ); } public int getSecsToLive() { int now_secs = (int)(SystemTime.getCurrentTime()/1000); if ( now_secs < create_time_secs ){ create_time_secs = now_secs; } return(( create_time_secs + timeout_secs ) - now_secs ); } protected String getString() { return( new String(ip) + ":" + getTCPPort() + "/" + getUDPPort() + "/" + getCryptoLevel()); } } private static class temporaryBiasedSeed implements TRTrackerServerSimplePeer { private String ip; private int tcp_port; private HashWrapper peer_id; protected temporaryBiasedSeed( String _ip, int _tcp_port ) { ip = _ip; tcp_port = _tcp_port; peer_id = new HashWrapper( RandomUtils.nextHash()); } public byte[] getIPAsRead() { try{ return( ip.getBytes( Constants.BYTE_ENCODING )); }catch( Throwable e ){ return( ip.getBytes()); } } public byte[] getIPAddressBytes() { try{ return( InetAddress.getByName( ip ).getAddress()); }catch( Throwable e ){ return( null ); } } public HashWrapper getPeerId() { return( peer_id ); } public int getTCPPort() { return( tcp_port ); } public int getUDPPort() { return( 0 ); } public int getHTTPPort() { return( 0 ); } public boolean isSeed() { return( true ); } public boolean isBiased() { return( true ); } public byte getCryptoLevel() { return( TRTrackerServerPeer.CRYPTO_NONE ); } public byte getAZVer() { return( 0 ); } public int getUpSpeed() { return( 0 ); } public DHTNetworkPosition getNetworkPosition() { return( null ); } } public String getString() { String redirect; if ( redirects == null ){ redirect = "none"; }else{ redirect = ""; for (int i=0;i<redirects.length;i++){ redirect += (i==0?"":",") + redirects[i]; } } return( "seeds=" + getSeedCount() + ",leechers=" + getLeecherCount() + ", redirect=" + redirect ); } }