/* * Created on Jan 29, 2007 * Created by Paul Gardner * Copyright (C) 2007 Aelitis, All Rights Reserved. * * 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, or (at your option) any later version. * 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. * 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. * * AELITIS, SAS au capital de 63.529,40 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package com.aelitis.azureus.plugins.tracker.peerauth; import java.io.BufferedInputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.StringWriter; import java.net.*; import java.util.*; import org.gudy.azureus2.core3.util.AERunnable; import org.gudy.azureus2.core3.util.BDecoder; import org.gudy.azureus2.core3.util.BEncoder; import org.gudy.azureus2.core3.util.Constants; import org.gudy.azureus2.core3.util.ThreadPool; import org.gudy.azureus2.plugins.Plugin; import org.gudy.azureus2.plugins.PluginInterface; import org.gudy.azureus2.plugins.download.Download; import org.gudy.azureus2.plugins.download.DownloadAnnounceResult; import org.gudy.azureus2.plugins.download.DownloadAnnounceResultPeer; import org.gudy.azureus2.plugins.download.DownloadManagerListener; import org.gudy.azureus2.plugins.download.DownloadPeerListener; import org.gudy.azureus2.plugins.download.DownloadScrapeResult; import org.gudy.azureus2.plugins.download.DownloadTrackerListener; import org.gudy.azureus2.plugins.logging.LoggerChannel; import org.gudy.azureus2.plugins.logging.LoggerChannelListener; import org.gudy.azureus2.plugins.peers.Peer; import org.gudy.azureus2.plugins.peers.PeerManager; import org.gudy.azureus2.plugins.peers.PeerManagerListener; import org.gudy.azureus2.plugins.torrent.Torrent; import org.gudy.azureus2.plugins.torrent.TorrentAttribute; import org.gudy.azureus2.plugins.ui.UIManager; import org.gudy.azureus2.plugins.ui.config.ConfigSection; import org.gudy.azureus2.plugins.ui.model.BasicPluginConfigModel; import org.gudy.azureus2.plugins.ui.model.BasicPluginViewModel; import com.aelitis.azureus.core.util.bloom.BloomFilter; import com.aelitis.azureus.core.util.bloom.BloomFilterFactory; public class TrackerPeerAuthPlugin implements Plugin, DownloadManagerListener { private static final String PLUGIN_NAME = "Tracker Peer Auth"; private static final String PLUGIN_CONFIGSECTION_ID = "Plugin.trackerpeerauth.name"; private static final int DEFAULT_CHECK_PERIOD = 30*1000; private static final String STATE_ENABLED = "enabled"; private static final String STATE_DISABLED = "disabled"; private static final int TIMER_PERIOD = 10*1000; private PluginInterface plugin_interface; private TorrentAttribute ta_state ; private LoggerChannel log; private Map dt_map = new HashMap(); private ThreadPool thread_pool = new ThreadPool("TrackerPeerAuthPlugin",8, true ); public static void load( PluginInterface plugin_interface ) { plugin_interface.getPluginProperties().setProperty( "plugin.version", "1.0" ); plugin_interface.getPluginProperties().setProperty( "plugin.name", PLUGIN_NAME ); } public void initialize( PluginInterface _plugin_interface ) { plugin_interface = _plugin_interface; ta_state = plugin_interface.getTorrentManager().getPluginAttribute( "state" ); log = plugin_interface.getLogger().getTimeStampedChannel(PLUGIN_NAME); UIManager ui_manager = plugin_interface.getUIManager(); BasicPluginConfigModel config = ui_manager.createBasicPluginConfigModel( ConfigSection.SECTION_PLUGINS, PLUGIN_CONFIGSECTION_ID ); config.addLabelParameter2( "Plugin.trackerpeerauth.info" ); final BasicPluginViewModel view_model = plugin_interface.getUIManager().createBasicPluginViewModel( "Plugin.trackerpeerauth.name" ); view_model.setConfigSectionID(PLUGIN_CONFIGSECTION_ID); view_model.getActivity().setVisible( false ); view_model.getProgress().setVisible( false ); log.addListener( new LoggerChannelListener() { public void messageLogged( int type, String content ) { view_model.getLogArea().appendText( content + "\n" ); } public void messageLogged( String str, Throwable error ) { if ( str.length() > 0 ){ view_model.getLogArea().appendText( str + "\n" ); } StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter( sw ); error.printStackTrace( pw ); pw.flush(); view_model.getLogArea().appendText( sw.toString() + "\n" ); } }); System.out.println( "**** tracker peer auth disabled ****" ); /* plugin_interface.getDownloadManager().addListener( this ); SimpleTimer.addPeriodicEvent( "TrackerPeerAuthPlugin:checker", TIMER_PERIOD, new TimerEventPerformer() { private long tick_count = 0; public void perform( TimerEvent event ) { tick_count++; synchronized( dt_map ){ Iterator it = dt_map.values().iterator(); while( it.hasNext()){ ((DownloadTracker)it.next()).checkPeers( tick_count ); } } } }); */ } public void downloadAdded( final Download download ) { Torrent torrent = download.getTorrent(); if ( torrent != null && torrent.isPrivate()){ download.addTrackerListener( new DownloadTrackerListener() { public void scrapeResult( DownloadScrapeResult result ) { } public void announceResult( DownloadAnnounceResult result ) { if ( result.getResponseType() == DownloadAnnounceResult.RT_SUCCESS ){ Map ext = result.getExtensions(); boolean enabled = true; int check_period = DEFAULT_CHECK_PERIOD; if ( ext != null ){ // TODO: detect enabled state // TODO: get check period } download.setAttribute( ta_state, enabled?STATE_ENABLED:STATE_DISABLED ); setState( download, enabled, check_period ); } } }); String state = download.getAttribute( ta_state ); if ( state != null ){ boolean enabled = state.equals( STATE_ENABLED ); setState( download, enabled, DEFAULT_CHECK_PERIOD ); } } } public void downloadRemoved( Download download ) { synchronized( dt_map ){ dt_map.remove( download ); } } protected void setState( Download download, boolean enabled, int check_period ) { synchronized( dt_map ){ if ( enabled ){ DownloadTracker existing = (DownloadTracker)dt_map.get( download ); if ( existing == null ){ DownloadTracker dt = new DownloadTracker( download, check_period ); dt_map.put( download, dt ); }else{ existing.setCheckPeriod( check_period ); } }else{ dt_map.remove( download ); } } } protected void log( Download download, String str ) { log.log( "Download '" + download.getName() + "' - " + str ); } protected class DownloadTracker implements DownloadPeerListener, PeerManagerListener, DownloadTrackerListener { private static final int BACKOFF_TICK_COUNT = 60*1000 / TIMER_PERIOD; // TODO: private static final int MAX_PEERS_PER_QUERY = 100; private static final int OK_BLOOM_INITIAL = 16*1024; private static final int OK_BLOOM_INC = 16*1024; private static final int OK_BLOOM_MAX = 128*1024; private static final int BAD_BLOOM_INITIAL = 4*1024; private static final int BAD_BLOOM_INC = 4*1024; private static final int BAD_BLOOM_MAX = 64*1024; private Download download; private BloomFilter ok_bloom = BloomFilterFactory.createAddOnly( OK_BLOOM_INITIAL ); private BloomFilter bad_bloom = BloomFilterFactory.createAddOnly( BAD_BLOOM_INITIAL ); private long pending_check_peer_count = 0; private boolean check_running; private int check_tick_count; private int backoff_tick_count; protected DownloadTracker( Download _download, int _min_check_period ) { download = _download; download.addTrackerListener( this ); download.addPeerListener( this ); setCheckPeriod( _min_check_period ); log( "enabled, check period=" + _min_check_period ); } protected void setCheckPeriod( int _min_check_period ) { check_tick_count = _min_check_period / TIMER_PERIOD; } protected void recordPeer( String source, byte[] id, String ip, int port, boolean ok ) { if ( id == null ){ return; } byte[] key = getKey( id, ip ); synchronized( this ){ if ( ok ){ int entries = ok_bloom.getEntryCount(); if ( entries > 0 && ( ok_bloom.getSize() / entries < 10 )){ int new_size = ok_bloom.getSize() + OK_BLOOM_INC; if ( new_size > OK_BLOOM_MAX ){ new_size = OK_BLOOM_MAX; } log( "Expanding ok bloom to " + new_size + " entries" ); BloomFilter new_ok_bloom = BloomFilterFactory.createAddOnly( new_size ); PeerManager pm = download.getPeerManager(); if ( pm != null ){ Peer[] peers = pm.getPeers(); for (int i=0;i<peers.length;i++){ byte[] peer_key = getKey( peers[i] ); if ( peer_key != null && ok_bloom.contains( peer_key )){ new_ok_bloom.add( peer_key ); } } } ok_bloom = new_ok_bloom; // need to drop the bad bloom here too. we rely on the ok bloom // to filter false positives on the bad bloom bad_bloom = BloomFilterFactory.createAddOnly( bad_bloom.getSize()); } ok_bloom.add( key ); }else{ int entries = bad_bloom.getEntryCount(); if ( entries > 0 && ( bad_bloom.getSize() / entries < 10 )){ int new_size = bad_bloom.getSize() + BAD_BLOOM_INC; if ( new_size > BAD_BLOOM_MAX ){ new_size = BAD_BLOOM_MAX; } log( "Expanding bad bloom to " + new_size + " entries" ); bad_bloom = BloomFilterFactory.createAddOnly( new_size ); } bad_bloom.add( key ); } } } protected void checkPeers( long tick_count ) { if ( backoff_tick_count > 0 ){ backoff_tick_count--; return; } if ( tick_count % check_tick_count == 0 ){ synchronized( this ){ if ( pending_check_peer_count > 0 && !check_running){ pending_check_peer_count = 0; check_running = true; }else{ return; } } boolean gone_async = false; try{ PeerManager pm = download.getPeerManager(); if ( pm != null ){ Peer[] peers = pm.getPeers(); final List to_check = new ArrayList(); for (int i=0;i<peers.length;i++){ Peer peer = peers[i]; byte[] peer_key = getKey( peer ); if ( peer_key != null ){ if ( ok_bloom.contains( peer_key )){ }else if ( bad_bloom.contains( peer_key )){ removePeer( peer ); }else{ to_check.add( peer ); } } } if ( to_check.size() > 0 ){ thread_pool.run( new AERunnable() { public void runSupport() { try{ check( to_check ); }finally{ synchronized( DownloadTracker.this ){ check_running = false; } } } }); gone_async = true; } } }finally{ if ( !gone_async ){ synchronized( this ){ check_running = false; } } } } } protected void check( List peers ) { DownloadAnnounceResult an = download.getLastAnnounceResult(); URL target = an==null?null:an.getURL(); if ( target == null ){ target = download.getTorrent().getAnnounceURL(); } OutputStreamWriter out = null; BufferedInputStream in = null; try{ String url_str = target.toString(); int pos = url_str.indexOf( "announce" ); if ( pos == -1 ){ // TODO: this should be logged once and checked earlier log( "announce URL '" + url_str + "' is non-conformant" ); return; } url_str = url_str.substring(0,pos) + "testauth" + url_str.substring( pos + 8 ); target = new URL( url_str ); Map map = new HashMap(); String peer_str = ""; for (int i=0;i<peers.size() && i < MAX_PEERS_PER_QUERY; i++ ){ Peer peer = (Peer)peers.get(i); List peer_data = new ArrayList(); peer_data.add( download.getTorrent().getHash()); peer_data.add( peer.getId()); peer_data.add( peer.getIp()); map.put( "peer" + i, peer_data ); peer_str += (i==0?"":",") + peer.getIp(); } log( "Checking " + url_str + " : peers=" + peer_str ); byte[] encoded = BEncoder.encode( map, true ); HttpURLConnection connection = (HttpURLConnection)target.openConnection(); String data = "authpeers=" + new String(encoded, "ISO-8859-1" ); System.out.println( "sending '" + data + "'" ); connection.setDoOutput(true); connection.setRequestMethod("POST"); connection.setRequestProperty("User-Agent", Constants.AZUREUS_NAME + " " + Constants.AZUREUS_VERSION); connection.setRequestProperty( "Connection", "close" ); connection.addRequestProperty( "Accept-Encoding", "gzip" ); out = new OutputStreamWriter(connection.getOutputStream()); out.write(data); out.flush(); in = new BufferedInputStream(connection.getInputStream()); Map result_map = BDecoder.decode( in ); for (int i=0;i<peers.size() && i < MAX_PEERS_PER_QUERY; i++ ){ Peer peer = (Peer)peers.get(i); Long enabled = (Long)result_map.get( "peer" + i ); if ( enabled == null ){ log( "No response for peer '" + peer.getIp() + "'" ); }else{ boolean ok = enabled.longValue() != 0; recordPeer( "auth check", peer.getId(), peer.getIp(), peer.getPort(), ok ); if ( !ok ){ removePeer( peer ); } } } }catch( Throwable e ){ backoff_tick_count = BACKOFF_TICK_COUNT; e.printStackTrace(); }finally{ if ( out != null ){ try{ out.close(); }catch( Throwable e ){ } } if ( in != null ){ try{ in.close(); }catch( Throwable e ){ } } } } protected byte[] getKey( Peer peer ) { byte[] peer_id = peer.getId(); if ( peer_id == null ){ return( null ); } return( getKey( peer_id, peer.getIp())); } protected byte[] getKey( byte[] peer_id, String ip_str ) { byte[] ip = ip_str.getBytes(); byte[] key = new byte[peer_id.length + ip.length ]; System.arraycopy( peer_id, 0, key, 0, peer_id.length ); System.arraycopy( ip, 0, key, peer_id.length, ip.length ); return( key ); } protected boolean knownToBeOK( Peer peer ) { byte[] peer_id = peer.getId(); if ( peer_id == null ){ // only happens on outbound connectas we don't retain the peer-id pending // outbound connect return( true ); } byte[] key = getKey( peer_id, peer.getIp()); return( ok_bloom.contains( key )); } protected boolean knownToBeBad( Peer peer ) { byte[] peer_id = peer.getId(); if ( peer_id == null ){ // shouldn't get here as we should check for OK first return( true ); } byte[] key = getKey( peer_id, peer.getIp()); return( bad_bloom.contains( key )); } protected void peerMightBeBad( Peer peer ) { if ( knownToBeOK( peer )){ }else if ( knownToBeBad( peer )){ removePeer( peer ); }else{ // we just leave it here and pick up for checking periodically pending_check_peer_count++; } } protected void removePeer( Peer peer ) { log( "Disconnecting peer " + peer.getIp() + "/" + peer.getPort() + ": not authorized" ); peer.close( "Tracker peer authorization failure", false, false ); } public void scrapeResult( DownloadScrapeResult result ) { } public void announceResult( DownloadAnnounceResult result ) { DownloadAnnounceResultPeer[] peers = result.getPeers(); if ( peers != null ){ for (int i=0;i<peers.length;i++){ DownloadAnnounceResultPeer peer = peers[i]; recordPeer( "Tracker", peer.getPeerID(), peer.getAddress(), peer.getPort(), true ); } } } public void peerAdded( PeerManager manager, Peer peer ) { // assume all outgoing connections are valid as we got them from the tracker System.out.println("adding peer froom trackerPeerAuthPlugin."); if ( peer.isIncoming()){ peerMightBeBad( peer ); }else{ recordPeer( "Outgoing", peer.getId(), peer.getIp(), peer.getPort(), true ); } } public void peerRemoved( PeerManager manager, Peer peer ) { } public void peerManagerAdded( Download download, PeerManager peer_manager ) { peer_manager.addListener( this ); } public void peerManagerRemoved( Download download, PeerManager peer_manager ) { peer_manager.removeListener( this ); } protected void log( String str ) { TrackerPeerAuthPlugin.this.log( download, str ); } } }