/* * Created on 24-Jan-2005 * Created by Paul Gardner * Copyright (C) 2004, 2005, 2006 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 46,603.30 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package com.aelitis.azureus.plugins.dht; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.*; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.util.*; import org.gudy.azureus2.plugins.*; import org.gudy.azureus2.plugins.logging.LoggerChannel; import org.gudy.azureus2.plugins.logging.LoggerChannelListener; import org.gudy.azureus2.plugins.ui.UIManager; import org.gudy.azureus2.plugins.ui.components.UITextField; import org.gudy.azureus2.plugins.ui.config.*; import org.gudy.azureus2.plugins.ui.model.BasicPluginConfigModel; import org.gudy.azureus2.plugins.ui.model.BasicPluginViewModel; import org.gudy.azureus2.plugins.utils.DelayedTask; import org.gudy.azureus2.plugins.utils.UTTimerEvent; import org.gudy.azureus2.plugins.utils.UTTimerEventPerformer; import com.aelitis.azureus.core.AzureusCoreFactory; import com.aelitis.azureus.core.dht.DHT; import com.aelitis.azureus.core.dht.DHTLogger; import com.aelitis.azureus.core.dht.control.DHTControlActivity; import com.aelitis.azureus.core.dht.control.DHTControlContact; import com.aelitis.azureus.core.dht.nat.DHTNATPuncher; import com.aelitis.azureus.core.dht.router.DHTRouterContact; import com.aelitis.azureus.core.dht.transport.DHTTransportContact; import com.aelitis.azureus.core.dht.transport.DHTTransportFullStats; import com.aelitis.azureus.core.dht.transport.udp.DHTTransportUDP; import com.aelitis.azureus.core.dht.transport.udp.impl.DHTTransportUDPImpl; import com.aelitis.azureus.core.networkmanager.admin.NetworkAdmin; import com.aelitis.azureus.core.networkmanager.impl.udp.UDPNetworkManager; import com.aelitis.azureus.core.versioncheck.VersionCheckClient; import com.aelitis.azureus.plugins.dht.impl.DHTPluginContactImpl; import com.aelitis.azureus.plugins.dht.impl.DHTPluginImpl; import com.aelitis.azureus.plugins.dht.impl.DHTPluginImplAdapter; import com.aelitis.azureus.plugins.upnp.UPnPMapping; import com.aelitis.azureus.plugins.upnp.UPnPPlugin; /** * @author parg * */ public class DHTPlugin implements Plugin { // data will be the DHT instance created public static final int EVENT_DHT_AVAILABLE = PluginEvent.PEV_FIRST_USER_EVENT; public static final int STATUS_DISABLED = 1; public static final int STATUS_INITALISING = 2; public static final int STATUS_RUNNING = 3; public static final int STATUS_FAILED = 4; public static final byte FLAG_SINGLE_VALUE = DHT.FLAG_SINGLE_VALUE; public static final byte FLAG_DOWNLOADING = DHT.FLAG_DOWNLOADING; public static final byte FLAG_SEEDING = DHT.FLAG_SEEDING; public static final byte FLAG_MULTI_VALUE = DHT.FLAG_MULTI_VALUE; public static final byte FLAG_STATS = DHT.FLAG_STATS; public static final byte FLAG_ANON = DHT.FLAG_ANON; public static final byte FLAG_PRECIOUS = DHT.FLAG_PRECIOUS; public static final byte DT_NONE = DHT.DT_NONE; public static final byte DT_FREQUENCY = DHT.DT_FREQUENCY; public static final byte DT_SIZE = DHT.DT_SIZE; public static final int NW_MAIN = DHT.NW_MAIN; public static final int NW_CVS = DHT.NW_CVS; public static final int MAX_VALUE_SIZE = DHT.MAX_VALUE_SIZE; private static final String PLUGIN_VERSION = "1.0"; private static final String PLUGIN_NAME = "Distributed DB"; private static final String PLUGIN_CONFIGSECTION_ID = "plugins.dht"; private static final String PLUGIN_RESOURCE_ID = "ConfigView.section.plugins.dht"; private static final boolean MAIN_DHT_ENABLE = COConfigurationManager.getBooleanParameter( "dht.net.main_v4.enable", true ); private static final boolean CVS_DHT_ENABLE = COConfigurationManager.getBooleanParameter( "dht.net.cvs_v4.enable", true ); private static final boolean MAIN_DHT_V6_ENABLE = COConfigurationManager.getBooleanParameter( "dht.net.main_v6.enable", true ); private PluginInterface plugin_interface; private int status = STATUS_DISABLED; private DHTPluginImpl[] dhts; private DHTPluginImpl main_dht; private DHTPluginImpl cvs_dht; private DHTPluginImpl main_v6_dht; private ActionParameter reseed; private boolean enabled; private int dht_data_port; private boolean got_extended_use; private boolean extended_use; private AESemaphore init_sem = new AESemaphore("DHTPlugin:init" ); private AEMonitor port_change_mon = new AEMonitor( "DHTPlugin:portChanger" ); private boolean port_changing; private int port_change_outstanding; private boolean[] ipfilter_logging = new boolean[1]; private BooleanParameter warn_user; private UPnPMapping upnp_mapping; private LoggerChannel log; private DHTLogger dht_log; private List listeners = new ArrayList(); private long start_mono_time = SystemTime.getMonotonousTime(); public static void load( PluginInterface plugin_interface ) { plugin_interface.getPluginProperties().setProperty( "plugin.version", PLUGIN_VERSION ); plugin_interface.getPluginProperties().setProperty( "plugin.name", PLUGIN_NAME ); } public void initialize( PluginInterface _plugin_interface ) { status = STATUS_INITALISING; plugin_interface = _plugin_interface; dht_data_port = UDPNetworkManager.getSingleton().getUDPNonDataListeningPortNumber(); log = plugin_interface.getLogger().getTimeStampedChannel(PLUGIN_NAME); UIManager ui_manager = plugin_interface.getUIManager(); final BasicPluginViewModel model = ui_manager.createBasicPluginViewModel( PLUGIN_RESOURCE_ID ); model.setConfigSectionID(PLUGIN_CONFIGSECTION_ID); BasicPluginConfigModel config = ui_manager.createBasicPluginConfigModel(ConfigSection.SECTION_PLUGINS, PLUGIN_CONFIGSECTION_ID); config.addLabelParameter2( "dht.info" ); final BooleanParameter enabled_param = config.addBooleanParameter2( "dht.enabled", "dht.enabled", true ); plugin_interface.getPluginconfig().addListener( new PluginConfigListener() { public void configSaved() { int new_dht_data_port = UDPNetworkManager.getSingleton().getUDPNonDataListeningPortNumber(); if ( new_dht_data_port != dht_data_port ){ changePort( new_dht_data_port ); } } }); LabelParameter reseed_label = config.addLabelParameter2( "dht.reseed.label" ); final StringParameter reseed_ip = config.addStringParameter2( "dht.reseed.ip", "dht.reseed.ip", "" ); final IntParameter reseed_port = config.addIntParameter2( "dht.reseed.port", "dht.reseed.port", 0 ); reseed = config.addActionParameter2( "dht.reseed.info", "dht.reseed"); reseed.setEnabled( false ); config.createGroup( "dht.reseed.group", new Parameter[]{ reseed_label, reseed_ip, reseed_port, reseed }); final BooleanParameter ipfilter_logging_param = config.addBooleanParameter2( "dht.ipfilter.log", "dht.ipfilter.log", true ); ipfilter_logging[0] = ipfilter_logging_param.getValue(); ipfilter_logging_param.addListener(new ParameterListener() { public void parameterChanged(Parameter p) { ipfilter_logging[0] = ipfilter_logging_param.getValue(); } }); warn_user = config.addBooleanParameter2( "dht.warn.user", "dht.warn.user", true ); final BooleanParameter advanced = config.addBooleanParameter2( "dht.advanced", "dht.advanced", false ); LabelParameter advanced_label = config.addLabelParameter2( "dht.advanced.label" ); final StringParameter override_ip = config.addStringParameter2( "dht.override.ip", "dht.override.ip", "" ); config.createGroup( "dht.advanced.group", new Parameter[]{ advanced_label, override_ip }); advanced.addEnabledOnSelection( advanced_label ); advanced.addEnabledOnSelection( override_ip ); final StringParameter command = config.addStringParameter2( "dht.execute.command", "dht.execute.command", "print" ); ActionParameter execute = config.addActionParameter2( "dht.execute.info", "dht.execute"); final BooleanParameter logging = config.addBooleanParameter2( "dht.logging", "dht.logging", false ); config.createGroup( "dht.diagnostics.group", new Parameter[]{ command, execute, logging }); logging.addListener( new ParameterListener() { public void parameterChanged( Parameter param ) { if ( dhts != null ){ for (int i=0;i<dhts.length;i++){ dhts[i].setLogging( logging.getValue()); } } } }); final DHTPluginOperationListener log_polistener = new DHTPluginOperationListener() { public boolean diversified() { return( true ); } public void starts( byte[] key) { } public void valueRead( DHTPluginContact originator, DHTPluginValue value ) { log.log( "valueRead: " + new String(value.getValue()) + " from " + originator.getName()); if ( ( value.getFlags() & DHTPlugin.FLAG_STATS ) != 0 ){ DHTPluginKeyStats stats = decodeStats( value ); log.log( " stats: size=" + (stats==null?"null":stats.getSize())); } } public void valueWritten( DHTPluginContact target, DHTPluginValue value ) { log.log( "valueWritten:" + new String( value.getValue()) + " to " + target.getName()); } public void complete( byte[] key, boolean timeout_occurred ) { log.log( "complete: timeout = " + timeout_occurred ); } }; execute.addListener( new ParameterListener() { public void parameterChanged( Parameter param ) { AEThread2 t = new AEThread2( "DHT:commandrunner", true ) { public void run() { if ( dhts == null ){ return; } String c = command.getValue().trim(); String lc = c.toLowerCase(); if ( lc.equals( "suspend" )){ if ( !setSuspended( true )){ Debug.out( "Suspend failed" ); } return; }else if ( lc.equals( "resume" )){ if ( !setSuspended( false )){ Debug.out( "Resume failed" ); } return; } for (int i=0;i<dhts.length;i++){ DHT dht = dhts[i].getDHT(); DHTTransportUDP transport = (DHTTransportUDP)dht.getTransport(); if ( lc.equals("print")){ dht.print( true ); dhts[i].logStats(); }else if ( lc.equals( "pingall" )){ if ( i == 1 ){ dht.getControl().pingAll(); } }else if ( lc.equals( "versions" )){ List<DHTRouterContact> contacts = dht.getRouter().getAllContacts(); Map<Byte,Integer> counts = new TreeMap<Byte, Integer>(); for ( DHTRouterContact r: contacts ){ DHTControlContact contact = (DHTControlContact)r.getAttachment(); byte v = contact.getTransportContact().getProtocolVersion(); Integer count = counts.get( v ); if ( count == null ){ counts.put( v, 1 ); }else{ counts.put( v, count+1 ); } } log.log( "Net " + dht.getTransport().getNetwork()); int total = contacts.size(); if ( total == 0 ){ log.log( " no contacts" ); }else{ String ver = ""; for ( Map.Entry<Byte, Integer> entry: counts.entrySet()){ ver += (ver.length()==0?"":", " ) + entry.getKey() + "=" + 100*entry.getValue()/total + "%"; } log.log( " contacts=" + total + ": " + ver ); } }else if ( lc.equals( "testca" )){ ((DHTTransportUDPImpl)transport).testExternalAddressChange(); }else if ( lc.equals( "testnd" )){ ((DHTTransportUDPImpl)transport).testNetworkAlive( false ); }else if ( lc.equals( "testna" )){ ((DHTTransportUDPImpl)transport).testNetworkAlive( true ); }else{ int pos = c.indexOf( ' ' ); if ( pos != -1 ){ String lhs = lc.substring(0,pos); String rhs = c.substring(pos+1); if ( lhs.equals( "set" )){ pos = rhs.indexOf( '=' ); if ( pos != -1 ){ DHTPlugin.this.put( rhs.substring(0,pos).getBytes(), "DHT Plugin: set", rhs.substring(pos+1).getBytes(), (byte)0, log_polistener ); } }else if ( lhs.equals( "get" )){ DHTPlugin.this.get( rhs.getBytes(), "DHT Plugin: get", (byte)0, 1, 10000, true, false, log_polistener ); }else if ( lhs.equals( "query" )){ DHTPlugin.this.get( rhs.getBytes(), "DHT Plugin: get", DHTPlugin.FLAG_STATS, 1, 10000, true, false, log_polistener ); }else if ( lhs.equals( "punch" )){ Map originator_data = new HashMap(); originator_data.put( "hello", "mum" ); DHTNATPuncher puncher = dht.getNATPuncher(); if ( puncher != null ){ puncher.punch( "Test", transport.getLocalContact(), null, originator_data ); } }else if ( lhs.equals( "stats" )){ try{ pos = rhs.lastIndexOf( ":" ); DHTTransportContact contact; if ( pos == -1 ){ contact = transport.getLocalContact(); }else{ String host = rhs.substring(0,pos); int port = Integer.parseInt( rhs.substring(pos+1)); contact = transport.importContact( new InetSocketAddress( host, port ), transport.getProtocolVersion(), false ); } log.log( "Stats request to " + contact.getName()); DHTTransportFullStats stats = contact.getStats(); log.log( "Stats:" + (stats==null?"<null>":stats.getString())); DHTControlActivity[] activities = dht.getControl().getActivities(); for (int j=0;j<activities.length;j++){ log.log( " act:" + activities[j].getString()); } }catch( Throwable e ){ Debug.printStackTrace(e); } } } } } } }; t.start(); } }); reseed.addListener( new ParameterListener() { public void parameterChanged( Parameter param ) { reseed.setEnabled( false ); AEThread2 t = new AEThread2( "DHT:reseeder", true ) { public void run() { try{ String ip = reseed_ip.getValue().trim(); if ( dhts == null ){ return; } int port = reseed_port.getValue(); for (int i=0;i<dhts.length;i++){ DHTPluginImpl dht = dhts[i]; if ( ip.length() == 0 || port == 0 ){ dht.checkForReSeed( true ); }else{ DHTTransportContact seed = dht.importSeed( ip, port ); if ( seed != null ){ dht.integrateDHT( false, seed ); } } } }finally{ reseed.setEnabled( true ); } } }; t.start(); } }); model.getActivity().setVisible( false ); model.getProgress().setVisible( false ); log.addListener( new LoggerChannelListener() { public void messageLogged( int type, String message ) { model.getLogArea().appendText( message+"\n"); } public void messageLogged( String str, Throwable error ) { model.getLogArea().appendText( error.toString()+"\n"); } }); dht_log = new DHTLogger() { public void log( String str ) { log.log( str ); } public void log( Throwable e ) { log.log( e ); } public void log( int log_type, String str ) { if ( isEnabled( log_type )){ log.log( str ); } } public boolean isEnabled( int log_type ) { if ( log_type == DHTLogger.LT_IP_FILTER ){ return ipfilter_logging[0]; } return( true ); } public PluginInterface getPluginInterface() { return( log.getLogger().getPluginInterface()); } }; if (!enabled_param.getValue()){ model.getStatus().setText( "Disabled" ); status = STATUS_DISABLED; init_sem.releaseForever(); return; } setPluginInfo(); plugin_interface.addListener( new PluginListener() { public void initializationComplete() { PluginInterface pi_upnp = plugin_interface.getPluginManager().getPluginInterfaceByClass( UPnPPlugin.class ); if ( pi_upnp == null ){ log.log( "UPnP plugin not found, can't map port" ); }else{ upnp_mapping = ((UPnPPlugin)pi_upnp.getPlugin()).addMapping( plugin_interface.getPluginName(), false, dht_data_port, true ); } String ip = null; if ( advanced.getValue()){ ip = override_ip.getValue().trim(); if ( ip.length() == 0 ){ ip = null; } } initComplete( model.getStatus(), logging.getValue(), ip ); } public void closedownInitiated() { if ( dhts != null ){ for (int i=0;i<dhts.length;i++){ dhts[i].closedownInitiated(); } } saveClockSkew(); } public void closedownComplete() { } }); final int sample_frequency = 60*1000; final int sample_stats_ticks = 15; // every 15 mins plugin_interface.getUtilities().createTimer("DHTStats", true ).addPeriodicEvent( sample_frequency, new UTTimerEventPerformer() { public void perform( UTTimerEvent event ) { if ( dhts != null ){ for (int i=0;i<dhts.length;i++){ dhts[i].updateStats( sample_stats_ticks ); } } setPluginInfo(); saveClockSkew(); } }); } protected void changePort( int _new_port ) { // don't check for new_port being dht_data_port here as we want to continue to pick up // changes that occurred during dht init try{ port_change_mon.enter(); port_change_outstanding = _new_port; if ( port_changing ){ return; } port_changing = true; }finally{ port_change_mon.exit(); } new AEThread2("DHTPlugin:portChanger", true ) { public void run() { while( true ){ int new_port; try{ port_change_mon.enter(); new_port = port_change_outstanding; }finally{ port_change_mon.exit(); } try{ dht_data_port = new_port; if ( upnp_mapping != null ){ if ( upnp_mapping.getPort() != new_port ){ upnp_mapping.setPort( new_port ); } } if ( status == STATUS_RUNNING ){ if ( dhts != null ){ for (int i=0;i<dhts.length;i++){ if ( dhts[i].getPort() != new_port ){ dhts[i].setPort( new_port ); } } } } }finally{ try{ port_change_mon.enter(); if ( new_port == port_change_outstanding ){ port_changing = false; break; } }finally{ port_change_mon.exit(); } } } } }.start(); } protected void initComplete( final UITextField status_area, final boolean logging, final String override_ip ) { AEThread2 t = new AEThread2( "DHTPlugin.init", true ) { public void run() { boolean went_async = false; try{ enabled = VersionCheckClient.getSingleton().DHTEnableAllowed(); if ( enabled ){ status_area.setText( "Initialising" ); final DelayedTask dt = plugin_interface.getUtilities().createDelayedTask(new Runnable() { public void run() { // go async again as don't want to block other tasks new AEThread2( "DHTPlugin.init2", true ) { public void run() { try{ List plugins = new ArrayList(); // adapter only added to first DHTPluginImpl we create DHTPluginImplAdapter adapter = new DHTPluginImplAdapter() { public void localContactChanged( DHTPluginContact local_contact ) { for (int i=0;i<listeners.size();i++){ ((DHTPluginListener)listeners.get(i)).localAddressChanged( local_contact ); } } }; if ( MAIN_DHT_ENABLE ){ main_dht = new DHTPluginImpl( plugin_interface, AzureusCoreFactory.getSingleton().getNATTraverser(), adapter, DHTTransportUDP.PROTOCOL_VERSION_MAIN, DHT.NW_MAIN, false, override_ip, dht_data_port, reseed, warn_user, logging, log, dht_log ); plugins.add( main_dht ); adapter = null; } if ( MAIN_DHT_V6_ENABLE ){ if ( NetworkAdmin.getSingleton().hasDHTIPV6()){ main_v6_dht = new DHTPluginImpl( plugin_interface, AzureusCoreFactory.getSingleton().getNATTraverser(), adapter, DHTTransportUDP.PROTOCOL_VERSION_MAIN, DHT.NW_MAIN_V6, true, null, dht_data_port, reseed, warn_user, logging, log, dht_log ); plugins.add( main_v6_dht ); adapter = null; } } if ( Constants.isCVSVersion() && CVS_DHT_ENABLE ){ cvs_dht = new DHTPluginImpl( plugin_interface, AzureusCoreFactory.getSingleton().getNATTraverser(), adapter, DHTTransportUDP.PROTOCOL_VERSION_CVS, DHT.NW_CVS, false, override_ip, dht_data_port, reseed, warn_user, logging, log, dht_log ); plugins.add( cvs_dht ); adapter = null; } DHTPluginImpl[] _dhts = new DHTPluginImpl[plugins.size()]; plugins.toArray( _dhts ); dhts = _dhts; status = dhts[0].getStatus(); status_area.setText( dhts[0].getStatusText()); }catch( Throwable e ){ enabled = false; status = STATUS_DISABLED; status_area.setText( "Disabled due to error during initialisation" ); log.log( e ); Debug.printStackTrace(e); }finally{ init_sem.releaseForever(); } // pick up any port changes that occurred during init if ( status == STATUS_RUNNING ){ changePort( dht_data_port ); } } }.start(); } }); dt.queue(); went_async = true; }else{ status = STATUS_DISABLED; status_area.setText( "Disabled administratively due to network problems" ); } }catch( Throwable e ){ enabled = false; status = STATUS_DISABLED; status_area.setText( "Disabled due to error during initialisation" ); log.log( e ); Debug.printStackTrace(e); }finally{ if ( !went_async ){ init_sem.releaseForever(); } } } }; t.start(); } protected void setPluginInfo() { boolean reachable = plugin_interface.getPluginconfig().getPluginBooleanParameter( "dht.reachable." + DHT.NW_MAIN, true ); plugin_interface.getPluginconfig().setPluginParameter( "plugin.info", reachable?"1":"0" ); } public boolean isEnabled() { if ( plugin_interface == null ){ Debug.out( "Called too early!" ); return false; } if ( plugin_interface.isInitialisationThread()){ if ( !init_sem.isReleasedForever()){ Debug.out( "Initialisation deadlock detected" ); return( true ); } } init_sem.reserve(); return( enabled ); } public boolean peekEnabled() { if ( init_sem.isReleasedForever()){ return( enabled ); } return( true ); // don't know yet } public boolean isInitialising() { return( !init_sem.isReleasedForever()); } public boolean setSuspended( final boolean susp ) { if ( !init_sem.isReleasedForever()){ return( false ); }else{ synchronized( this ){ for ( DHTPluginImpl dht: dhts ){ dht.setSuspended( susp ); } } } return( true ); } public boolean isExtendedUseAllowed() { if ( !isEnabled()){ return( false ); } if ( !got_extended_use){ got_extended_use = true; extended_use = VersionCheckClient.getSingleton().DHTExtendedUseAllowed(); } return( extended_use ); } public boolean isReachable() { if ( !isEnabled()){ throw( new RuntimeException( "DHT isn't enabled" )); } return( dhts[0].isReachable()); } public boolean isDiversified( byte[] key ) { if ( !isEnabled()){ throw( new RuntimeException( "DHT isn't enabled" )); } return( dhts[0].isDiversified( key )); } public void put( byte[] key, String description, byte[] value, byte flags, DHTPluginOperationListener listener) { put( key, description, value, flags, true, listener ); } public void put( final byte[] key, final String description, final byte[] value, final byte flags, final boolean high_priority, final DHTPluginOperationListener listener) { if ( !isEnabled()){ throw( new RuntimeException( "DHT isn't enabled" )); } if( dhts.length == 1 ){ dhts[0].put( key, description, value, flags, high_priority, listener ); }else{ final int[] completes_to_go = { dhts.length }; DHTPluginOperationListener main_listener = new DHTPluginOperationListener() { public boolean diversified() { return( listener.diversified()); } public void starts( byte[] key ) { listener.starts(key); } public void valueRead( DHTPluginContact originator, DHTPluginValue value ) { listener.valueRead(originator, value); } public void valueWritten( DHTPluginContact target, DHTPluginValue value ) { listener.valueWritten(target, value); } public void complete( byte[] key, boolean timeout_occurred ) { synchronized( completes_to_go ){ completes_to_go[0]--; if ( completes_to_go[0] == 0 ){ listener.complete(key, timeout_occurred); } } } }; dhts[0].put( key, description, value, flags, high_priority, main_listener ); for (int i=1;i<dhts.length;i++){ dhts[i].put( key, description, value, flags, high_priority, new DHTPluginOperationListener() { public boolean diversified() { return( true ); } public void starts( byte[] key ) { } public void valueRead( DHTPluginContact originator, DHTPluginValue value ) { } public void valueWritten( DHTPluginContact target, DHTPluginValue value ) { } public void complete( byte[] key, boolean timeout_occurred ) { synchronized( completes_to_go ){ completes_to_go[0]--; if ( completes_to_go[0] == 0 ){ listener.complete(key, timeout_occurred); } } } }); } } } public DHTPluginValue getLocalValue( byte[] key ) { if ( main_dht != null ){ return( main_dht.getLocalValue( key )); }else if ( cvs_dht != null ){ return( cvs_dht.getLocalValue( key )); }else if ( main_v6_dht != null ){ return( main_v6_dht.getLocalValue( key )); }else{ return( null ); } } public List<DHTPluginValue> getValues() { if ( main_dht != null ){ return( main_dht.getValues()); }else if ( cvs_dht != null ){ return( cvs_dht.getValues()); }else if ( main_v6_dht != null ){ return( main_v6_dht.getValues()); }else{ return( new ArrayList<DHTPluginValue>()); } } public void get( final byte[] original_key, final String description, final byte flags, final int max_values, final long timeout, final boolean exhaustive, final boolean high_priority, final DHTPluginOperationListener original_listener ) { if ( !isEnabled()){ throw( new RuntimeException( "DHT isn't enabled" )); } final DHTPluginOperationListener main_listener; if ( cvs_dht == null ){ main_listener = original_listener; }else{ if ( main_dht == null && main_v6_dht == null ){ // just the cvs dht cvs_dht.get( original_key, description, flags, max_values, timeout, exhaustive, high_priority, original_listener ); return; } // hook into CVS completion to prevent runaway CVS dht operations final int[] completes_to_go = { 2 }; final boolean[] main_timeout = { false }; main_listener = new DHTPluginOperationListener() { public boolean diversified() { return( original_listener.diversified()); } public void starts( byte[] key ) { original_listener.starts( original_key ); } public void valueRead( DHTPluginContact originator, DHTPluginValue value ) { original_listener.valueRead(originator, value); } public void valueWritten( DHTPluginContact target, DHTPluginValue value ) { original_listener.valueWritten(target, value); } public void complete( byte[] key, boolean timeout_occurred ) { synchronized( completes_to_go ){ completes_to_go[0]--; main_timeout[0] = timeout_occurred; if ( completes_to_go[0] == 0 ){ original_listener.complete( original_key, timeout_occurred ); } } } }; cvs_dht.get( original_key, description, flags, max_values, timeout, exhaustive, high_priority, new DHTPluginOperationListener() { public boolean diversified() { return( true ); } public void starts( byte[] key ) { } public void valueRead( DHTPluginContact originator, DHTPluginValue value ) { } public void valueWritten( DHTPluginContact target, DHTPluginValue value ) { } public void complete( byte[] key, boolean timeout_occurred ) { synchronized( completes_to_go ){ completes_to_go[0]--; if ( completes_to_go[0] == 0 ){ original_listener.complete( original_key, main_timeout[0] ); } } } }); } if ( main_dht != null && main_v6_dht == null ){ main_dht.get( original_key, description, flags, max_values, timeout, exhaustive, high_priority, main_listener ); }else if ( main_dht == null && main_v6_dht != null ){ main_v6_dht.get( original_key, description, flags, max_values, timeout, exhaustive, high_priority, main_listener ); }else{ // both DHTs active. Initially (at least :) V6 is going to be very sparse. We therefore // don't want to be blocking the "get" operation waiting for V6 to timeout when V4 is // returning hits final byte[] v4_key = original_key; final byte[] v6_key = (byte[])original_key.clone(); DHTPluginOperationListener dual_listener = new DHTPluginOperationListener() { private long start_time = SystemTime.getCurrentTime(); private boolean started; private int complete_count = 0; private int result_count = 0; public boolean diversified() { return( main_listener.diversified()); } public void starts( byte[] key ) { synchronized( this ){ if ( started ){ return; } started = true; } main_listener.starts( original_key ); } public void valueRead( DHTPluginContact originator, DHTPluginValue value ) { synchronized( this ){ result_count++; // only report if not yet complete if ( complete_count < 2 ){ main_listener.valueRead( originator, value ); } } } public void valueWritten( DHTPluginContact target, DHTPluginValue value ) { Debug.out( "eh?" ); } public void complete( final byte[] timeout_key, final boolean timeout_occurred ) { // we are guaranteed to come through here at least twice synchronized( this ){ complete_count++; if ( complete_count == 2 ){ // if we have reported any results then we can't report // timeout! main_listener.complete( original_key, result_count>0?false:timeout_occurred ); return; }else if ( complete_count > 2 ){ return; } // One of the two gets, see how much longer we're happy to hang around for // Only of interest if timeout then uninterested as the other will be // about to timeout if ( timeout_occurred ){ return; } // ignore a v6 completion ahead of a v4 if ( timeout_key == v6_key ){ return; } long now = SystemTime.getCurrentTime(); long elapsed = now - start_time; long rem = timeout - elapsed; if ( rem <= 0 ){ complete( timeout_key, true ); }else{ SimpleTimer.addEvent( "DHTPlugin:dual_dht_early_timeout", now + rem, new TimerEventPerformer() { public void perform( TimerEvent event) { complete( timeout_key, true ); } }); } } } }; // hack - use different keys so we can distinguish which completion event we // have received above main_dht.get( v4_key, description, flags, max_values, timeout, exhaustive, high_priority, dual_listener ); main_v6_dht.get( v6_key, description, flags, max_values, timeout, exhaustive, high_priority, dual_listener ); } } public boolean hasLocalKey( byte[] hash ) { if ( !isEnabled()){ throw( new RuntimeException( "DHT isn't enabled" )); } return( dhts[0].getLocalValue( hash ) != null ); } public void remove( final byte[] key, final String description, final DHTPluginOperationListener listener ) { if ( !isEnabled()){ throw( new RuntimeException( "DHT isn't enabled" )); } dhts[0].remove( key, description, listener ); for (int i=1;i<dhts.length;i++){ final int f_i = i; new AEThread2( "multi-dht: remove", true ) { public void run() { dhts[f_i].remove( key, description, new DHTPluginOperationListener() { public boolean diversified() { return( true ); } public void starts( byte[] key ) { } public void valueRead( DHTPluginContact originator, DHTPluginValue value ) { } public void valueWritten( DHTPluginContact target, DHTPluginValue value ) { } public void complete( byte[] key, boolean timeout_occurred ) { } }); } }.start(); } } public void remove( DHTPluginContact[] targets, byte[] key, String description, DHTPluginOperationListener listener ) { if ( !isEnabled()){ throw( new RuntimeException( "DHT isn't enabled" )); } Map dht_map = new HashMap(); for (int i=0;i<targets.length;i++){ DHTPluginContactImpl target = (DHTPluginContactImpl)targets[i]; DHTPluginImpl dht = target.getDHT(); List c = (List)dht_map.get(dht); if ( c == null ){ c = new ArrayList(); dht_map.put( dht, c ); } c.add( target ); } Iterator it = dht_map.entrySet().iterator(); boolean primary = true; while( it.hasNext()){ Map.Entry entry = (Map.Entry)it.next(); DHTPluginImpl dht = (DHTPluginImpl)entry.getKey(); List contacts = (List)entry.getValue(); DHTPluginContact[] dht_targets = new DHTPluginContact[contacts.size()]; contacts.toArray( dht_targets ); if ( primary ){ primary = false; dht.remove( dht_targets, key, description, listener ); }else{ // lazy - just report ops on one dht dht.remove( dht_targets, key, description, new DHTPluginOperationListener() { public boolean diversified() { return( true ); } public void starts( byte[] key ) { } public void valueRead( DHTPluginContact originator, DHTPluginValue value ) { } public void valueWritten( DHTPluginContact target, DHTPluginValue value ) { } public void complete( byte[] key, boolean timeout_occurred ) { } }); } } } public DHTPluginContact importContact( InetSocketAddress address ) { if ( !isEnabled()){ throw( new RuntimeException( "DHT isn't enabled" )); } // first DHT will do here return( dhts[0].importContact( address )); } public DHTPluginContact importContact( InetSocketAddress address, byte version ) { if ( !isEnabled()){ throw( new RuntimeException( "DHT isn't enabled" )); } InetAddress contact_address = address.getAddress(); for ( DHTPluginImpl dht: dhts ){ InetAddress dht_address = dht.getLocalAddress().getAddress().getAddress(); if ( ( contact_address instanceof Inet4Address && dht_address instanceof Inet4Address ) || ( contact_address instanceof Inet6Address && dht_address instanceof Inet6Address )){ return( dht.importContact( address, version )); } } return( null ); } public DHTPluginContact importContact( InetSocketAddress address, byte version, boolean is_cvs ) { if ( !isEnabled()){ throw( new RuntimeException( "DHT isn't enabled" )); } InetAddress contact_address = address.getAddress(); int target_network = is_cvs?DHT.NW_CVS:DHT.NW_MAIN; for ( DHTPluginImpl dht: dhts ){ if ( dht.getDHT().getTransport().getNetwork() != target_network ){ continue; } InetAddress dht_address = dht.getLocalAddress().getAddress().getAddress(); if ( ( contact_address instanceof Inet4Address && dht_address instanceof Inet4Address ) || ( contact_address instanceof Inet6Address && dht_address instanceof Inet6Address )){ return( dht.importContact( address, version )); } } // fallback return( importContact( address, version )); } public DHTPluginContact getLocalAddress() { if ( !isEnabled()){ throw( new RuntimeException( "DHT isn't enabled" )); } // first DHT will do here return( dhts[0].getLocalAddress()); } // direct read/write support public void registerHandler( byte[] handler_key, final DHTPluginTransferHandler handler ) { if ( !isEnabled()){ throw( new RuntimeException( "DHT isn't enabled" )); } for (int i=0;i<dhts.length;i++){ dhts[i].registerHandler( handler_key, handler ); } } public int getStatus() { return( status ); } public boolean isSleeping() { return( AERunStateHandler.isDHTSleeping()); } public DHT[] getDHTs() { if ( dhts == null ){ return( new DHT[0] ); } DHT[] res = new DHT[ dhts.length ]; for (int i=0;i<res.length;i++){ res[i] = dhts[i].getDHT(); } return( res ); } public DHT getDHT( int network ) { if ( dhts == null ){ return( null ); } for (int i=0;i<dhts.length;i++){ if ( dhts[i].getDHT().getTransport().getNetwork() == network ){ return( dhts[i].getDHT()); } } return( null ); } protected long loadClockSkew() { return( plugin_interface.getPluginconfig().getPluginLongParameter( "dht.skew", 0 )); } protected void saveClockSkew() { long existing = loadClockSkew(); long current = getClockSkew(); if ( Math.abs( existing - current ) > 5000 ){ plugin_interface.getPluginconfig().setPluginParameter( "dht.skew", getClockSkew()); } } public long getClockSkew() { if ( dhts == null || dhts.length == 0 ){ return( 0 ); } long uptime = SystemTime.getMonotonousTime() - start_mono_time; if ( uptime < 5*60*1000 ){ return( loadClockSkew()); } long skew = dhts[0].getClockSkew(); if ( skew > 24*60*60*1000 ){ skew = 0; } skew = ( skew/500 )*500; return( skew ); } public DHTPluginKeyStats decodeStats( DHTPluginValue value ) { return( dhts[0].decodeStats( value )); } public void addListener( DHTPluginListener l ) { listeners.add(l); } public void removeListener( DHTPluginListener l ) { listeners.remove(l); } public void log( String str ) { log.log( str ); } }