/*
* Created on Jan 20, 2005
* Created by Alon Rohter
* Copyright (C) 2004-2005 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.core.peermanager;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.*;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.logging.*;
import org.gudy.azureus2.core3.peer.PEPeer;
import org.gudy.azureus2.core3.peer.PEPeerListener;
import org.gudy.azureus2.core3.peer.PEPeerSource;
import org.gudy.azureus2.core3.peer.impl.*;
import org.gudy.azureus2.core3.peer.util.PeerIdentityManager;
import org.gudy.azureus2.core3.torrent.TOTorrentFile;
import org.gudy.azureus2.core3.util.AEMonitor;
import org.gudy.azureus2.core3.util.AEThread2;
import org.gudy.azureus2.core3.util.ByteFormatter;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.HashWrapper;
import org.gudy.azureus2.core3.util.SystemTime;
import com.aelitis.azureus.core.networkmanager.*;
import com.aelitis.azureus.core.networkmanager.impl.IncomingConnectionManager;
import com.aelitis.azureus.core.networkmanager.impl.TransportHelper;
import com.aelitis.azureus.core.peermanager.download.TorrentDownload;
import com.aelitis.azureus.core.peermanager.download.TorrentDownloadFactory;
import com.aelitis.azureus.core.peermanager.messaging.*;
import com.aelitis.azureus.core.peermanager.messaging.bittorrent.*;
import com.aelitis.azureus.core.peermanager.piecepicker.util.BitFlags;
import com.aelitis.azureus.core.stats.AzureusCoreStats;
import com.aelitis.azureus.core.stats.AzureusCoreStatsProvider;
import com.aelitis.azureus.core.util.bloom.BloomFilter;
import com.aelitis.azureus.core.util.bloom.BloomFilterFactory;
/**
*
*/
public class PeerManager implements AzureusCoreStatsProvider{
private static final LogIDs LOGID = LogIDs.PEER;
private static final PeerManager instance = new PeerManager();
private static final int PENDING_TIMEOUT = 10*1000;
private static final AEMonitor timer_mon = new AEMonitor( "PeerManager:timeouts" );
private static AEThread2 timer_thread;
private static Set timer_targets = new HashSet();
protected static void
registerForTimeouts(
PeerManagerRegistrationImpl reg )
{
try{
timer_mon.enter();
timer_targets.add( reg );
if ( timer_thread == null ){
timer_thread =
new AEThread2( "PeerManager:timeouts", true )
{
public void
run()
{
int idle_time = 0;
while( true ){
try{
Thread.sleep( PENDING_TIMEOUT / 2 );
}catch( Throwable e ){
}
try{
timer_mon.enter();
if ( timer_targets.size() == 0 ){
idle_time += PENDING_TIMEOUT / 2;
if ( idle_time >= 30*1000 ){
timer_thread = null;
break;
}
}else{
idle_time = 0;
Iterator it = timer_targets.iterator();
while( it.hasNext()){
PeerManagerRegistrationImpl registration = (PeerManagerRegistrationImpl)it.next();
if ( !registration.timeoutCheck()){
it.remove();
}
}
}
}finally{
timer_mon.exit();
}
}
}
};
timer_thread.start();
}
}finally{
timer_mon.exit();
}
}
/**
* Get the singleton instance of the peer manager.
* @return the peer manager
*/
public static PeerManager getSingleton() { return instance; }
private final Map registered_legacy_managers = new HashMap();
private final Map registered_links = new HashMap();
private final ByteBuffer legacy_handshake_header;
private final AEMonitor managers_mon = new AEMonitor( "PeerManager:managers" );
private PeerManager() {
legacy_handshake_header = ByteBuffer.allocate( 20 );
legacy_handshake_header.put( (byte)BTHandshake.PROTOCOL.length() );
legacy_handshake_header.put( BTHandshake.PROTOCOL.getBytes() );
legacy_handshake_header.flip();
Set types = new HashSet();
types.add( AzureusCoreStats.ST_PEER_MANAGER_COUNT );
types.add( AzureusCoreStats.ST_PEER_MANAGER_PEER_COUNT );
types.add( AzureusCoreStats.ST_PEER_MANAGER_PEER_SNUBBED_COUNT );
types.add( AzureusCoreStats.ST_PEER_MANAGER_PEER_STALLED_DISK_COUNT );
AzureusCoreStats.registerProvider( types, this );
init();
}
public void
updateStats(
Set types,
Map values )
{
if ( types.contains( AzureusCoreStats.ST_PEER_MANAGER_COUNT )){
values.put( AzureusCoreStats.ST_PEER_MANAGER_COUNT, new Long( registered_legacy_managers.size()));
}
if ( types.contains( AzureusCoreStats.ST_PEER_MANAGER_PEER_COUNT ) ||
types.contains( AzureusCoreStats.ST_PEER_MANAGER_PEER_SNUBBED_COUNT ) ||
types.contains( AzureusCoreStats.ST_PEER_MANAGER_PEER_STALLED_DISK_COUNT )){
long total_peers = 0;
long total_snubbed_peers = 0;
long total_stalled_pending_load = 0;
try{
managers_mon.enter();
Iterator it = registered_legacy_managers.values().iterator();
while( it.hasNext()){
List registrations = (List)it.next();
Iterator it2 = registrations.iterator();
while( it2.hasNext()){
PeerManagerRegistrationImpl reg = (PeerManagerRegistrationImpl)it2.next();
PEPeerControl control = reg.getActiveControl();
if ( control != null ){
total_peers += control.getNbPeers();
total_snubbed_peers += control.getNbPeersSnubbed();
total_stalled_pending_load += control.getNbPeersStalledPendingLoad();
}
}
}
}finally{
managers_mon.exit();
}
if ( types.contains( AzureusCoreStats.ST_PEER_MANAGER_PEER_COUNT )){
values.put( AzureusCoreStats.ST_PEER_MANAGER_PEER_COUNT, new Long( total_peers ));
}
if ( types.contains( AzureusCoreStats.ST_PEER_MANAGER_PEER_SNUBBED_COUNT )){
values.put( AzureusCoreStats.ST_PEER_MANAGER_PEER_SNUBBED_COUNT, new Long( total_snubbed_peers ));
}
if ( types.contains( AzureusCoreStats.ST_PEER_MANAGER_PEER_STALLED_DISK_COUNT )){
values.put( AzureusCoreStats.ST_PEER_MANAGER_PEER_STALLED_DISK_COUNT, new Long( total_stalled_pending_load ));
}
}
}
protected void
init()
{
MessageManager.getSingleton().initialize(); //ensure it gets initialized
NetworkManager.ByteMatcher matcher =
new NetworkManager.ByteMatcher()
{
public int matchThisSizeOrBigger(){ return( 48 );}
public int maxSize() { return 48; }
public int minSize() { return 20; }
public Object
matches(
TransportHelper transport,
ByteBuffer to_compare,
int port )
{
InetSocketAddress address = transport.getAddress();
int old_limit = to_compare.limit();
int old_position = to_compare.position();
to_compare.limit( old_position + 20 );
PeerManagerRegistrationImpl routing_data = null;
if( to_compare.equals( legacy_handshake_header ) ) { //compare header
to_compare.limit( old_position + 48 );
to_compare.position( old_position + 28 );
byte[] hash = new byte[to_compare.remaining()];
to_compare.get( hash );
try{
managers_mon.enter();
List registrations = (List)registered_legacy_managers.get( new HashWrapper( hash ));
if ( registrations != null ){
routing_data = (PeerManagerRegistrationImpl)registrations.get(0);
}
}finally{
managers_mon.exit();
}
}
//restore buffer structure
to_compare.limit( old_limit );
to_compare.position( old_position );
if ( routing_data != null ){
if ( !routing_data.isActive()){
if ( routing_data.isKnownSeed( address )){
if (Logger.isEnabled()){
Logger.log(new LogEvent(LOGID, "Activation request from " + address + " denied as known seed" ));
}
routing_data = null;
}else{
if ( !routing_data.getAdapter().activateRequest( address )){
if (Logger.isEnabled()){
Logger.log(new LogEvent(LOGID, "Activation request from " + address + " denied by rules" ));
}
routing_data = null;
}
}
}
}
return routing_data;
}
public Object
minMatches(
TransportHelper transport,
ByteBuffer to_compare,
int port )
{
boolean matches = false;
int old_limit = to_compare.limit();
int old_position = to_compare.position();
to_compare.limit( old_position + 20 );
if( to_compare.equals( legacy_handshake_header ) ) {
matches = true;
}
//restore buffer structure
to_compare.limit( old_limit );
to_compare.position( old_position );
return matches?"":null;
}
public byte[][]
getSharedSecrets()
{
return( null ); // registered manually above
}
public int
getSpecificPort()
{
return( -1 );
}
};
// register for incoming connection routing
NetworkManager.getSingleton().requestIncomingConnectionRouting(
matcher,
new NetworkManager.RoutingListener()
{
public void
connectionRouted(
NetworkConnection connection,
Object routing_data )
{
PeerManagerRegistrationImpl registration = (PeerManagerRegistrationImpl)routing_data;
registration.route( connection, null );
}
public boolean
autoCryptoFallback()
{
return( false );
}
},
new MessageStreamFactory() {
public MessageStreamEncoder createEncoder() { return new BTMessageEncoder(); }
public MessageStreamDecoder createDecoder() { return new BTMessageDecoder(); }
});
}
public PeerManagerRegistration
manualMatchHash(
InetSocketAddress address,
byte[] hash )
{
PeerManagerRegistrationImpl routing_data = null;
try{
managers_mon.enter();
List registrations = (List)registered_legacy_managers.get( new HashWrapper( hash ));
if ( registrations != null ){
routing_data = (PeerManagerRegistrationImpl)registrations.get(0);
}
}finally{
managers_mon.exit();
}
if ( routing_data != null ){
if ( !routing_data.isActive()){
if ( routing_data.isKnownSeed( address )){
if (Logger.isEnabled()){
Logger.log(new LogEvent(LOGID, "Activation request from " + address + " denied as known seed" ));
}
routing_data = null;
}else{
if ( !routing_data.getAdapter().activateRequest( address )){
if (Logger.isEnabled()){
Logger.log(new LogEvent(LOGID, "Activation request from " + address + " denied by rules" ));
}
routing_data = null;
}
}
}
}
return routing_data;
}
public PeerManagerRegistration
manualMatchLink(
InetSocketAddress address,
String link )
{
byte[] hash;
try{
managers_mon.enter();
PeerManagerRegistrationImpl registration = (PeerManagerRegistrationImpl)registered_links.get( link );
if ( registration == null ){
return( null );
}
hash = registration.getHash();
}finally{
managers_mon.exit();
}
return( manualMatchHash( address, hash ));
}
public void
manualRoute(
PeerManagerRegistration _registration,
NetworkConnection _connection,
PeerManagerRoutingListener _listener )
{
PeerManagerRegistrationImpl registration = (PeerManagerRegistrationImpl)_registration;
registration.route( _connection, _listener );
}
public PeerManagerRegistration
registerLegacyManager(
HashWrapper hash,
PeerManagerRegistrationAdapter adapter )
{
try{
managers_mon.enter();
// normally we only get a max of 1 of these. However, due to DownloadManager crazyness
// we can get an extra one when adding a download that already exists...
List registrations = (List)registered_legacy_managers.get( hash );
byte[][] secrets = adapter.getSecrets();
if ( registrations == null ){
registrations = new ArrayList(1);
registered_legacy_managers.put( hash, registrations );
IncomingConnectionManager.getSingleton().addSharedSecrets( secrets );
}
PeerManagerRegistration registration = new PeerManagerRegistrationImpl( hash, adapter );
registrations.add( registration );
return( registration );
}finally{
managers_mon.exit();
}
}
private class
PeerManagerRegistrationImpl
implements PeerManagerRegistration
{
private HashWrapper hash;
private PeerManagerRegistrationAdapter adapter;
private TorrentDownload download;
private volatile PEPeerControl active_control;
private List pending_connections;
private BloomFilter known_seeds;
private Map links;
protected
PeerManagerRegistrationImpl(
HashWrapper _hash,
PeerManagerRegistrationAdapter _adapter )
{
hash = _hash;
adapter = _adapter;
}
protected PeerManagerRegistrationAdapter
getAdapter()
{
return( adapter );
}
protected byte[]
getHash()
{
return( hash.getBytes() );
}
public synchronized TOTorrentFile
getLink(
String target )
{
if ( links == null ){
return( null );
}
return((TOTorrentFile)links.get(target));
}
public void
addLink(
String link,
TOTorrentFile target )
throws Exception
{
try{
managers_mon.enter();
if ( registered_links.get( link ) != null ){
throw( new Exception( "Duplicate link '" + link + "'" ));
}
registered_links.put( link, this );
System.out.println( "Added link '" + link + "'" );
}finally{
managers_mon.exit();
}
synchronized( this ){
if ( links == null ){
links = new HashMap();
}
links.put( link, target );
}
}
public void
removeLink(
String link )
{
try{
managers_mon.enter();
registered_links.remove( link );
}finally{
managers_mon.exit();
}
synchronized( this ){
if ( links != null ){
links.remove( link );
}
}
}
public boolean
isActive()
{
return( active_control != null );
}
public void
activate(
PEPeerControl _active_control )
{
List connections = null;
try{
managers_mon.enter();
active_control = _active_control;
if ( download != null ){
Debug.out( "Already activated" );
}
download = TorrentDownloadFactory.getSingleton().createDownload( active_control ); //link legacy with new
connections = pending_connections;
pending_connections = null;
}finally{
managers_mon.exit();
}
if ( connections != null ){
for (int i=0;i<connections.size();i++){
Object[] entry = (Object[])connections.get(i);
NetworkConnection nc = (NetworkConnection)entry[0];
PeerManagerRoutingListener listener = (PeerManagerRoutingListener)entry[2];
route( _active_control, nc, true, listener );
}
}
}
public void
deactivate()
{
try{
managers_mon.enter();
if ( download == null ){
Debug.out( "Already deactivated" );
}else{
download.destroy(); //break legacy link
download = null;
}
active_control = null;
if ( pending_connections != null ){
for (int i=0;i<pending_connections.size();i++){
Object[] entry = (Object[])pending_connections.get(i);
NetworkConnection connection = (NetworkConnection)entry[0];
if (Logger.isEnabled())
Logger.log(new LogEvent(LOGID, LogEvent.LT_WARNING,
"Incoming connection from [" + connection
+ "] closed due to deactivation" ));
connection.close();
}
pending_connections = null;
}
}finally{
managers_mon.exit();
}
}
public void
unregister()
{
try{
managers_mon.enter();
if ( active_control != null ){
Debug.out( "Not deactivated" );
deactivate();
}
List registrations = (List)registered_legacy_managers.get( hash );
if ( registrations == null ){
Debug.out( "manager already deregistered" );
}else{
if ( registrations.remove( this )){
if ( registrations.size() == 0 ){
IncomingConnectionManager.getSingleton().removeSharedSecrets( adapter.getSecrets());
registered_legacy_managers.remove( hash );
}
}else{
Debug.out( "manager already deregistered" );
}
}
if ( links != null ){
Iterator it = links.keySet().iterator();
while( it.hasNext()){
registered_links.remove( it.next());
}
}
}finally{
managers_mon.exit();
}
}
protected boolean
isKnownSeed(
InetSocketAddress address )
{
try{
managers_mon.enter();
if ( known_seeds == null ){
return( false );
}
return( known_seeds.contains( address.getAddress().getAddress()));
}finally{
managers_mon.exit();
}
}
protected void
setKnownSeed(
InetSocketAddress address )
{
try{
managers_mon.enter();
if ( known_seeds == null ){
known_seeds = BloomFilterFactory.createAddOnly( 1024 );
}
// can't include port as it will be a randomly allocated one in general. two people behind the
// same NAT will have to connect to each other using LAN peer finder
known_seeds.add( address.getAddress().getAddress() );
}finally{
managers_mon.exit();
}
}
protected PEPeerControl
getActiveControl()
{
return( active_control );
}
protected void
route(
NetworkConnection connection,
PeerManagerRoutingListener listener )
{
if ( adapter.manualRoute( connection )){
return;
}
if ( !adapter.isPeerSourceEnabled( PEPeerSource.PS_INCOMING )){
if (Logger.isEnabled())
Logger.log(new LogEvent(LOGID, LogEvent.LT_WARNING,
"Incoming connection from [" + connection
+ "] to " + adapter.getDescription() + " dropped as peer source disabled" ));
connection.close();
return;
}
PEPeerControl control;
boolean register_for_timeouts = false;
try{
managers_mon.enter();
control = active_control;
if ( control == null ){
// not yet activated, queue connection for use on activation
if ( pending_connections != null && pending_connections.size() > 10 ){
if (Logger.isEnabled())
Logger.log(new LogEvent(LOGID, LogEvent.LT_WARNING,
"Incoming connection from [" + connection
+ "] to " + adapter.getDescription() + " dropped too many pending activations" ));
connection.close();
return;
}else{
if ( pending_connections == null ){
pending_connections = new ArrayList();
}
pending_connections.add( new Object[]{ connection, new Long( SystemTime.getCurrentTime()), listener });
if ( pending_connections.size() == 1 ){
register_for_timeouts = true;
}
}
}
}finally{
managers_mon.exit();
}
// do this outside the monitor as the timeout code calls us back holding the timeout monitor
// and we need to grab managers_mon inside this to run timeouts
if ( register_for_timeouts ){
registerForTimeouts( this );
}
if ( control != null ){
route( control, connection, false, listener );
}
}
protected boolean
timeoutCheck()
{
try{
managers_mon.enter();
if ( pending_connections == null ){
return( false );
}
Iterator it = pending_connections.iterator();
long now = SystemTime.getCurrentTime();
while( it.hasNext()){
Object[] entry = (Object[])it.next();
long start_time = ((Long)entry[1]).longValue();
if ( now < start_time ){
entry[1] = new Long( now );
}else if ( now - start_time > PENDING_TIMEOUT ){
it.remove();
NetworkConnection connection = (NetworkConnection)entry[0];
if (Logger.isEnabled())
Logger.log(new LogEvent(LOGID, LogEvent.LT_WARNING,
"Incoming connection from [" + connection
+ "] to " + adapter.getDescription() + " closed due to activation timeout" ));
connection.close();
}
}
if ( pending_connections.size() == 0 ){
pending_connections = null;
}
return( pending_connections != null );
}finally{
managers_mon.exit();
}
}
protected void
route(
PEPeerControl control,
final NetworkConnection connection,
boolean is_activation,
PeerManagerRoutingListener listener )
{
// make sure not already connected to the same IP address; allow
// loopback connects for co-located proxy-based connections and
// testing
String host_address = connection.getEndpoint().getNotionalAddress().getAddress().getHostAddress();
boolean same_allowed = COConfigurationManager.getBooleanParameter( "Allow Same IP Peers" ) || host_address.equals( "127.0.0.1" );
if( !same_allowed && PeerIdentityManager.containsIPAddress( control.getPeerIdentityDataID(), host_address ) ){
if (Logger.isEnabled())
Logger.log(new LogEvent(LOGID, LogEvent.LT_WARNING,
"Incoming connection from [" + connection
+ "] dropped as IP address already "
+ "connected for ["
+ control.getDisplayName() + "]"));
connection.close();
return;
}
if (Logger.isEnabled())
Logger.log(new LogEvent(LOGID, "Incoming connection from ["
+ connection + "] routed to legacy download ["
+ control.getDisplayName() + "]"));
PEPeerTransport pt = PEPeerTransportFactory.createTransport( control, PEPeerSource.PS_INCOMING, connection, null );
if ( listener != null ){
boolean ok = false;
try{
if ( listener.routed( pt )){
ok = true;
}
}catch( Throwable e ){
Debug.printStackTrace(e);
}
if ( !ok ){
connection.close();
return;
}
}
pt.start();
if ( is_activation ){
pt.addListener(
new PEPeerListener()
{
public void
stateChanged(
PEPeer peer,
int new_state )
{
if ( new_state == PEPeer.CLOSING ){
if ( peer.isSeed()){
InetSocketAddress address = connection.getEndpoint().getNotionalAddress();
setKnownSeed( address );
// this is mainly to deal with seeds that incorrectly connect to us
adapter.deactivateRequest( address );
}
}
}
public void sentBadChunk(PEPeer peer, int piece_num, int total_bad_chunks ){}
public void addAvailability(final PEPeer peer, final BitFlags peerHavePieces){}
public void removeAvailability(final PEPeer peer, final BitFlags peerHavePieces){}
});
}
control.addPeerTransport( pt );
}
public String
getDescription()
{
PEPeerControl control = active_control;
return( ByteFormatter.encodeString( hash.getBytes()) + ", control=" + (control==null?null:control.getDisplayName()) + ": " + adapter.getDescription());
}
}
}