/* * Created on Feb 15, 2008 * Created by Paul Gardner * * Copyright 2008 Vuze, Inc. 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; version 2 of the License only. * * 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. */ package com.aelitis.azureus.plugins.net.netstatus; import java.net.InetSocketAddress; import java.util.*; import org.gudy.azureus2.core3.util.AERunnable; import org.gudy.azureus2.core3.util.AESemaphore; import org.gudy.azureus2.core3.util.AEThread2; 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.DelayedEvent; import org.gudy.azureus2.core3.util.HashWrapper; import org.gudy.azureus2.core3.util.SimpleTimer; 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.core3.util.TimerEventPeriodic; import org.gudy.azureus2.plugins.PluginInterface; import org.gudy.azureus2.plugins.ddb.DistributedDatabase; import org.gudy.azureus2.plugins.ddb.DistributedDatabaseContact; import org.gudy.azureus2.plugins.ddb.DistributedDatabaseException; import org.gudy.azureus2.plugins.ddb.DistributedDatabaseKey; import org.gudy.azureus2.plugins.ddb.DistributedDatabaseProgressListener; import org.gudy.azureus2.plugins.ddb.DistributedDatabaseTransferHandler; import org.gudy.azureus2.plugins.ddb.DistributedDatabaseTransferType; import org.gudy.azureus2.plugins.ddb.DistributedDatabaseValue; import com.aelitis.azureus.core.dht.DHT; import com.aelitis.azureus.core.dht.transport.DHTTransportContact; import com.aelitis.azureus.core.networkmanager.NetworkManager; import com.aelitis.azureus.core.networkmanager.impl.tcp.TCPNetworkManager; import com.aelitis.azureus.plugins.dht.DHTPlugin; import com.aelitis.azureus.plugins.net.netstatus.NetStatusProtocolTesterBT.Session; public class NetStatusProtocolTester implements DistributedDatabaseTransferHandler { private static final int REQUEST_HISTORY_MAX = 64; private static final int MAX_ACTIVE_TESTS = 3; private static final int MAX_TEST_TIME = 120*1000; private static final int TEST_TYPE_BT = 1; private static final int VERSION_INITIAL = 1; private static final int CURRENT_VERSION = VERSION_INITIAL; private static final int BT_MAX_SLAVES = 8; private NetStatusPlugin plugin; private PluginInterface plugin_interface; private DistributedDatabase ddb; private DHTPlugin dht_plugin; private testXferType transfer_type = new testXferType(); private Map request_history = new LinkedHashMap(REQUEST_HISTORY_MAX,0.75f,true) { protected boolean removeEldestEntry( Map.Entry eldest) { return size() > REQUEST_HISTORY_MAX; } }; private List active_tests = new ArrayList(); private TimerEventPeriodic timer_event = null; protected NetStatusProtocolTester( NetStatusPlugin _plugin, PluginInterface _plugin_interface ) { plugin = _plugin; plugin_interface = _plugin_interface; try{ PluginInterface dht_pi = plugin_interface.getPluginManager().getPluginInterfaceByClass( DHTPlugin.class ); if ( dht_pi != null ){ dht_plugin = (DHTPlugin)dht_pi.getPlugin(); } ddb = plugin_interface.getDistributedDatabase(); ddb.addTransferHandler( transfer_type, this ); log( "DDB transfer type registered" ); }catch( Throwable e ){ log( "DDB transfer type registration failed", e ); } } public NetStatusProtocolTesterBT runTest( final NetStatusProtocolTesterListener listener ) { return( runTest( "", listener )); } public NetStatusProtocolTesterBT runTest( String test_address, final NetStatusProtocolTesterListener listener ) { final NetStatusProtocolTesterBT bt_tester = new NetStatusProtocolTesterBT( this, true ); bt_tester.addListener( listener ); bt_tester.start(); addToActive( bt_tester ); try{ if ( test_address.length() == 0 ){ DHT[] dhts = dht_plugin.getDHTs(); DHT target_dht = null; int target_network = Constants.isCVSVersion()?DHT.NW_CVS:DHT.NW_MAIN; for (int i=0;i<dhts.length;i++){ if ( dhts[i].getTransport().getNetwork() == target_network ){ target_dht = dhts[i]; break; } } if ( target_dht == null ){ listener.logError( "Distributed database unavailable" ); }else{ DHTTransportContact[] contacts = target_dht.getTransport().getReachableContacts(); final List f_contacts = new ArrayList(Arrays.asList(contacts)); final int[] ok = new int[]{ 0 }; final int num_threads = Math.min( BT_MAX_SLAVES, contacts.length ); listener.log( "Searching " + contacts.length + " contacts for " + num_threads + " test targets" ); final AESemaphore sem = new AESemaphore( "NetStatusProbe" ); for (int i=0;i<num_threads;i++){ new AEThread2( "NetStatusProbe", true ) { public void run() { try{ while( !bt_tester.isDestroyed()){ DHTTransportContact contact = null; synchronized( ok ){ if ( ok[0] < num_threads && f_contacts.size() > 0 ){ contact = (DHTTransportContact)f_contacts.remove(0); } } if ( contact == null ){ break; } try{ DistributedDatabaseContact ddb_contact = ddb.importContact( contact.getAddress()); if ( tryTest( bt_tester, ddb_contact )){ synchronized( ok ){ ok[0]++; } } }catch( Throwable e ){ listener.logError( "Contact import for " + contact.getName() + " failed", e ); } } }finally{ sem.release(); } } }.start(); } for (int i=0;i<num_threads;i++){ sem.reserve(); } listener.log( "Searching complete, " + ok[0] + " targets found" ); } }else{ String[] bits = test_address.split( ":" ); if ( bits.length != 2 ){ log( "Invalid address - use <host>:<port> " ); return( bt_tester ); } InetSocketAddress address = new InetSocketAddress( bits[0].trim(), Integer.parseInt( bits[1].trim())); DistributedDatabaseContact contact = ddb.importContact( address ); tryTest( bt_tester, contact ); } }catch( Throwable e ){ listener.logError( "Test failed", e ); }finally{ bt_tester.addListener( new NetStatusProtocolTesterListener() { public void sessionAdded( Session session) { } public void complete( NetStatusProtocolTesterBT tester ) { removeFromActive( tester ); } public void log( String str ) { } public void logError( String str ) { } public void logError( String str, Throwable e ) { } }); bt_tester.setOutboundConnectionsComplete(); new DelayedEvent( "NetStatus:killer", 10*1000, new AERunnable() { public void runSupport() { listener.log( "Destroying tester" ); bt_tester.destroy(); } }); } return( bt_tester ); } protected boolean tryTest( NetStatusProtocolTesterBT bt_tester, DistributedDatabaseContact contact ) { boolean use_crypto = NetworkManager.getCryptoRequired( NetworkManager.CRYPTO_OVERRIDE_NONE ); log( "Trying test to " + contact.getName()); Map request = new HashMap(); request.put( "v", new Long( CURRENT_VERSION )); request.put( "t", new Long( TEST_TYPE_BT )); request.put( "h", bt_tester.getServerHash()); request.put( "c", new Long( use_crypto?1:0 )); Map reply = sendRequest( contact, request ); byte[] server_hash = reply==null?null:(byte[])reply.get( "h" ); if ( server_hash != null ){ log( " " + contact.getName() + " accepted test" ); bt_tester.testOutbound( adjustLoopback( contact.getAddress()), server_hash, use_crypto ); return( true ); }else{ log( " " + contact.getName() + " declined test" ); return( false ); } } protected InetSocketAddress adjustLoopback( InetSocketAddress address ) { InetSocketAddress local = dht_plugin.getLocalAddress().getAddress(); if ( local.getAddress().getHostAddress().equals( address.getAddress().getHostAddress())){ return( new InetSocketAddress( "127.0.0.1", address.getPort())); }else{ return( address ); } } protected Map sendRequest( DistributedDatabaseContact contact, Map request ) { try{ log( "Sending DDB request to " + contact.getName() + " - " + request ); DistributedDatabaseKey key = ddb.createKey( BEncoder.encode( request )); DistributedDatabaseValue value = contact.read( new DistributedDatabaseProgressListener() { public void reportSize( long size ) { } public void reportActivity( String str ) { } public void reportCompleteness( int percent ) { } }, transfer_type, key, 10000 ); if ( value == null ){ return( null ); } Map reply = BDecoder.decode((byte[])value.getValue( byte[].class )); log( " received reply - " + reply ); return( reply ); }catch( Throwable e ){ log( "sendRequest failed", e ); return( null ); } } protected Map receiveRequest( InetSocketAddress originator, Map request ) { Map reply = new HashMap(); Long test_type = (Long)request.get( "t" ); reply.put( "v", new Long( CURRENT_VERSION )); if ( test_type != null ){ if ( test_type.intValue() == TEST_TYPE_BT ){ TCPNetworkManager tcp_man = TCPNetworkManager.getSingleton(); InetSocketAddress adjusted_originator = adjustLoopback( originator ); boolean test = adjusted_originator.getAddress().isLoopbackAddress(); if ( test || ( tcp_man.isTCPListenerEnabled() && tcp_man.getTCPListeningPortNumber() == ddb.getLocalContact().getAddress().getPort() && SystemTime.getCurrentTime() - tcp_man.getLastIncomingNonLocalConnectionTime() <= 24*60*60*1000 )){ byte[] their_hash = (byte[])request.get( "h" ); if ( their_hash != null ){ NetStatusProtocolTesterBT bt_tester; synchronized( active_tests ){ if ( active_tests.size() > MAX_ACTIVE_TESTS ){ log( "Too many active tests" ); return( reply ); }else{ bt_tester = new NetStatusProtocolTesterBT( this, false ); bt_tester.start(); addToActive( bt_tester ); } } Long l_crypto = (Long)request.get( "c" ); boolean use_crypto = l_crypto!=null&&l_crypto.longValue()==1; bt_tester.testOutbound( adjusted_originator, their_hash, use_crypto ); reply.put( "h", bt_tester.getServerHash()); } } } } return( reply ); } protected void addToActive( NetStatusProtocolTesterBT tester ) { synchronized( active_tests ){ active_tests.add( tester ); if ( timer_event == null ){ timer_event = SimpleTimer.addPeriodicEvent( "NetStatusProtocolTester:timer", 30*1000, new TimerEventPerformer() { public void perform( TimerEvent event ) { long now = SystemTime.getCurrentTime(); List to_remove = new ArrayList(); synchronized( active_tests ){ for (int i=0;i<active_tests.size();i++){ NetStatusProtocolTesterBT tester = (NetStatusProtocolTesterBT)active_tests.get(i); long start = tester.getStartTime( now ); if ( now - start > MAX_TEST_TIME ){ to_remove.add( tester ); } } } for ( int i=0;i<to_remove.size();i++ ){ removeFromActive( (NetStatusProtocolTesterBT)to_remove.get(i)); } } }); } } } protected void removeFromActive( NetStatusProtocolTesterBT tester ) { tester.destroy(); synchronized( active_tests ){ active_tests.remove( tester ); if ( active_tests.size() == 0 ){ if ( timer_event != null ){ timer_event.cancel(); timer_event = null; } } } } public DistributedDatabaseValue read( DistributedDatabaseContact contact, DistributedDatabaseTransferType type, DistributedDatabaseKey ddb_key ) throws DistributedDatabaseException { Object o_key = ddb_key.getKey(); try{ byte[] key = (byte[])o_key; HashWrapper hw = new HashWrapper( key ); synchronized( request_history ){ if ( request_history.containsKey( hw )){ return( null ); } request_history.put( hw, "" ); } Map request = BDecoder.decode( (byte[])o_key); log( "Received DDB request from " + contact.getName() + " - " + request ); Map result = receiveRequest( contact.getAddress(), request ); return( ddb.createValue( BEncoder.encode( result ))); }catch( Throwable e ){ log( "DDB read failed", e ); return( null ); } } public void write( DistributedDatabaseContact contact, DistributedDatabaseTransferType type, DistributedDatabaseKey key, DistributedDatabaseValue value ) throws DistributedDatabaseException { throw( new DistributedDatabaseException( "not supported" )); } public void log( String str ) { plugin.log( str ); } public void log( String str, Throwable e ) { plugin.log( str, e ); } protected class testXferType implements DistributedDatabaseTransferType { } }