/* * File : TRTrackerServerProcessor.java * Created : 20-Jan-2004 * By : parg * * 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.io.UnsupportedEncodingException; import java.util.*; import org.gudy.azureus2.core3.logging.LogEvent; import org.gudy.azureus2.core3.logging.LogIDs; import org.gudy.azureus2.core3.logging.Logger; import org.gudy.azureus2.core3.tracker.server.*; import org.gudy.azureus2.core3.tracker.util.TRTrackerUtils; import org.gudy.azureus2.core3.config.*; import org.gudy.azureus2.core3.util.*; import com.aelitis.azureus.core.dht.netcoords.DHTNetworkPosition; public abstract class TRTrackerServerProcessor extends ThreadPoolTask { private static final boolean QUEUE_TEST = false; static{ if ( QUEUE_TEST ){ System.out.println( "**** TRTrackerServerProcessor::QUEUE_TEST ****" ); } } private TRTrackerServerImpl server; private long start; private int request_type; protected TRTrackerServerTorrentImpl processTrackerRequest( TRTrackerServerImpl _server, String request, Map[] root_out, // output TRTrackerServerPeerImpl[] peer_out, // output int _request_type, byte[][] hashes, String link, String scrape_flags, HashWrapper peer_id, boolean no_peer_id, byte compact_mode, String key, String event, boolean stop_to_queue, int port, int udp_port, int http_port, String real_ip_address, String original_client_ip_address, long downloaded, long uploaded, long left, int num_want, byte crypto_level, byte az_ver, int up_speed, DHTNetworkPosition network_position ) throws TRTrackerServerException { server = _server; request_type = _request_type; if ( !server.isReady()){ throw( new TRTrackerServerException( "Tracker initialising, please wait" )); } start = SystemTime.getHighPrecisionCounter(); boolean ip_override = real_ip_address != original_client_ip_address; boolean loopback = TRTrackerUtils.isLoopback( real_ip_address ); if ( loopback ){ // any override is purely for routing purposes for loopback connections and we don't // want to apply the ip-override precedence rules against us ip_override = false; } // translate any 127.0.0.1 local addresses back to the tracker address. Note this // fixes up .i2p and onion addresses back to their real values when needed String client_ip_address = TRTrackerUtils.adjustHostFromHosting( original_client_ip_address ); if ( client_ip_address != original_client_ip_address ){ if ( Logger.isEnabled()){ Logger.log( new LogEvent(LogIDs.TRACKER, " address adjusted: original=" + original_client_ip_address + ", real=" + real_ip_address + ", adjusted=" + client_ip_address + ", loopback=" + loopback )); } } if ( !TRTrackerServerImpl.getAllNetworksSupported()){ String network = AENetworkClassifier.categoriseAddress( client_ip_address ); String[] permitted_networks = TRTrackerServerImpl.getPermittedNetworks(); boolean ok = false; for (int i=0;i<permitted_networks.length;i++){ if ( network == permitted_networks[i] ){ ok = true; break; } } if ( !ok ){ throw( new TRTrackerServerException( "Network '" + network + "' not supported" )); } } TRTrackerServerTorrentImpl torrent = null; if ( request_type != TRTrackerServerRequest.RT_FULL_SCRAPE ){ // System.out.println( "TRTrackerServerProcessor::request:" + request_type + ",event:" + event + " - " + client_ip_address + ":" + port ); // System.out.println( " hash = " + ByteFormatter.nicePrint(hash)); if ( request_type == TRTrackerServerRequest.RT_ANNOUNCE ){ if ( hashes == null || hashes.length == 0 ){ throw( new TRTrackerServerException( "Hash missing from request ")); } if ( hashes.length != 1 ){ throw( new TRTrackerServerException( "Too many hashes for announce")); } byte[] hash = hashes[0]; torrent = server.getTorrent( hash ); if ( torrent == null ){ if ( !COConfigurationManager.getBooleanParameter( "Tracker Public Enable")){ throw( new TRTrackerServerException( "Torrent unauthorised" )); }else{ try{ torrent = (TRTrackerServerTorrentImpl)server.permit( real_ip_address, hash, false ); }catch( Throwable e ){ throw( new TRTrackerServerException( "Torrent unauthorised", e )); } } } if ( peer_id == null ){ throw( new TRTrackerServerException( "peer_id missing from request")); } boolean queue_it = stop_to_queue; if ( queue_it ){ Set biased = server.getBiasedPeers(); if ( biased != null && biased.contains( real_ip_address )){ // biased peers get to queue whatever }else{ if ( loopback || ip_override ){ queue_it = false; } } } long interval; long min_interval; if ( queue_it ){ // when queued we use the scrape timeouts as it is scrape operations that // will keep the entry alive from this point on interval = server.getScrapeRetryInterval( torrent ); min_interval = server.getMinScrapeRetryInterval(); }else{ interval = server.getAnnounceRetryInterval( torrent ); min_interval = server.getMinAnnounceRetryInterval(); if ( left == 0 ){ long mult = server.getSeedAnnounceIntervalMultiplier(); interval *= mult; min_interval *= mult; } } TRTrackerServerPeerImpl peer = torrent.peerContact( request, event, peer_id, port, udp_port, http_port, crypto_level, az_ver, real_ip_address, client_ip_address, ip_override, loopback, key, uploaded, downloaded, left, interval, up_speed, network_position ); if ( queue_it ){ torrent.peerQueued( client_ip_address, port, udp_port, http_port, crypto_level, az_ver, interval, left==0 ); } HashMap pre_map = new HashMap(); TRTrackerServerPeer pre_process_peer = peer; if ( pre_process_peer == null ){ // can be null for stop events received without a previous start pre_process_peer = new lightweightPeer(client_ip_address,port,peer_id); } server.preProcess( pre_process_peer, torrent, request_type, request, pre_map ); // set num_want to 0 for stopped events as no point in returning peers boolean stopped = event != null && event.equalsIgnoreCase("stopped"); root_out[0] = torrent.exportAnnounceToMap( client_ip_address, pre_map, peer, left > 0, stopped?0:num_want, interval, min_interval, no_peer_id, compact_mode, crypto_level, network_position ); peer_out[0] = peer; }else if ( request_type == TRTrackerServerRequest.RT_QUERY ){ if ( link == null ){ if ( hashes == null || hashes.length == 0 ){ throw( new TRTrackerServerException( "Hash missing from request ")); } if ( hashes.length != 1 ){ throw( new TRTrackerServerException( "Too many hashes for query")); } byte[] hash = hashes[0]; torrent = server.getTorrent( hash ); }else{ torrent = server.getTorrent( link ); } if ( torrent == null ){ throw( new TRTrackerServerException( "Torrent unauthorised" )); } long interval = server.getAnnounceRetryInterval( torrent ); root_out[0] = torrent.exportAnnounceToMap( client_ip_address, new HashMap(), null, true, num_want, interval, server.getMinAnnounceRetryInterval(), true, compact_mode, crypto_level, network_position ); }else{ if ( hashes == null || hashes.length == 0 ){ throw( new TRTrackerServerException( "Hash missing from request ")); } boolean local_scrape = client_ip_address.equals( "127.0.0.1" ); long max_interval = server.getMinScrapeRetryInterval(); Map root = new HashMap(); root_out[0] = root; Map files = new ByteEncodedKeyHashMap(); root.put( "files", files ); char[] scrape_chars = scrape_flags==null?null:scrape_flags.toCharArray(); if ( scrape_chars != null && scrape_chars.length != hashes.length ){ scrape_chars = null; } for (int i=0;i<hashes.length;i++){ byte[] hash = hashes[i]; String str_hash; try{ str_hash = new String( hash, Constants.BYTE_ENCODING ); // skip duplicates if ( i > 0 && files.get( str_hash ) != null ){ continue; } }catch( UnsupportedEncodingException e ){ continue; } torrent = server.getTorrent( hash ); if ( torrent == null ){ if ( !COConfigurationManager.getBooleanParameter( "Tracker Public Enable")){ continue; }else{ try{ torrent = (TRTrackerServerTorrentImpl)server.permit( real_ip_address, hash, false ); }catch( Throwable e ){ continue; } } } long interval = server.getScrapeRetryInterval( torrent ); if ( interval > max_interval ){ max_interval = interval; } if ( scrape_chars != null && ( QUEUE_TEST || !( loopback || ip_override ))){ // note, 'Q' is complete+queued so we set seed true below if ( scrape_chars[i] == 'Q' ){ torrent.peerQueued( client_ip_address, port, udp_port, http_port, crypto_level, az_ver, (int)interval, true ); } } if ( torrent.getRedirects() != null ){ if ( hashes.length > 1 ){ // just drop this from the set. this will cause the client to revert // to single-hash scrapes and subsequently pick up the redirect continue; } } server.preProcess( new lightweightPeer(client_ip_address,port,peer_id), torrent, request_type, request, null ); // we don't cache local scrapes as if we do this causes the hosting of // torrents to retrieve old values initially. Not a fatal error but not // the best behaviour as the (local) seed isn't initially visible. Map hash_entry = torrent.exportScrapeToMap( request, client_ip_address, !local_scrape ); // System.out.println( "tracker - encoding: " + ByteFormatter.nicePrint(torrent_hash) + " -> " + ByteFormatter.nicePrint( str_hash.getBytes( Constants.BYTE_ENCODING ))); files.put( str_hash, hash_entry ); } if ( hashes.length > 1 ){ torrent = null; // no specific torrent } // System.out.println( "scrape: hashes = " + hashes.length + ", files = " + files.size() + ", tim = " + max_interval ); addScrapeInterval( max_interval, root ); } }else{ if ( !TRTrackerServerImpl.isFullScrapeEnabled()){ throw( new TRTrackerServerException( "Full scrape disabled" )); } Map files = new ByteEncodedKeyHashMap(); TRTrackerServerTorrentImpl[] torrents = server.getTorrents(); for (int i=0;i<torrents.length;i++){ TRTrackerServerTorrentImpl this_torrent = torrents[i]; if ( this_torrent.getRedirects() != null ){ // not visible to a full-scrape continue; } server.preProcess( new lightweightPeer(client_ip_address,port,peer_id), this_torrent, request_type, request, null ); byte[] torrent_hash = this_torrent.getHash().getHash(); try{ String str_hash = new String( torrent_hash,Constants.BYTE_ENCODING ); // System.out.println( "tracker - encoding: " + ByteFormatter.nicePrint(torrent_hash) + " -> " + ByteFormatter.nicePrint( str_hash.getBytes( Constants.BYTE_ENCODING ))); Map hash_entry = this_torrent.exportScrapeToMap( request, client_ip_address, true ); files.put( str_hash, hash_entry ); }catch( UnsupportedEncodingException e ){ throw( new TRTrackerServerException( "Encoding error", e )); } } Map root = new HashMap(); root_out[0] = root; addScrapeInterval( null, root ); root.put( "files", files ); } return( torrent ); } protected void addScrapeInterval( TRTrackerServerTorrentImpl torrent, Map root ) { long interval = server.getScrapeRetryInterval( torrent ); addScrapeInterval( interval, root ); } protected void addScrapeInterval( long interval, Map root ) { if ( interval > 0 ){ Map flags = new HashMap(); flags.put("min_request_interval", new Long(interval)); root.put( "flags", flags ); } } public void taskCompleted() { if ( start > 0 ){ long time = SystemTime.getHighPrecisionCounter() - start; server.updateTime( request_type, time ); } } protected static class lightweightPeer implements TRTrackerServerPeer { private String ip; private int port; private byte[] peer_id; public lightweightPeer( String _ip, int _port, HashWrapper _peer_id ) { ip = _ip; port = _port; peer_id = _peer_id==null?null:_peer_id.getBytes(); } public long getUploaded() { return( -1 ); } public long getDownloaded() { return( -1 ); } public long getAmountLeft() { return( -1 ); } public String getIP() { return( ip ); } public String getIPRaw() { return( ip ); } public byte getNATStatus() { return( NAT_CHECK_UNKNOWN ); } public int getTCPPort() { return( port ); } public int getHTTPPort() { return( 0 ); } public int getUDPPort() { return( 0 ); } public byte[] getPeerID() { return( peer_id ); } public boolean isBiased() { return( false ); } public void setBiased( boolean biased ) { } public void setUserData( Object key, Object data ) { } public Object getUserData( Object key ) { return( null ); } public int getSecsToLive() { return( -1 ); } public Map export() { return( null ); } } }