/* * Created on May 8, 2004 * Created by Alon Rohter * 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.core.networkmanager.impl.tcp; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.HashMap; import java.util.Map; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.logging.*; import org.gudy.azureus2.core3.util.*; import com.aelitis.azureus.core.networkmanager.*; import com.aelitis.azureus.core.networkmanager.impl.ProtocolDecoder; import com.aelitis.azureus.core.networkmanager.impl.TransportHelperFilter; import com.aelitis.azureus.core.networkmanager.impl.TransportCryptoManager; import com.aelitis.azureus.core.networkmanager.impl.TransportHelper; import com.aelitis.azureus.core.networkmanager.impl.TransportImpl; import com.aelitis.azureus.core.proxy.AEProxyFactory; import com.aelitis.azureus.core.proxy.AEProxyFactory.PluginProxy; /** * Represents a peer TCP transport connection (eg. a network socket). */ public class TCPTransportImpl extends TransportImpl implements Transport { private static final LogIDs LOGID = LogIDs.NET; private final ProtocolEndpointTCP protocol_endpoint; private TCPConnectionManager.ConnectListener connect_request_key = null; private String description = "<disconnected>"; private final boolean is_inbound_connection; private int transport_mode = TRANSPORT_MODE_NORMAL; public volatile boolean has_been_closed = false; private boolean connect_with_crypto; private byte[][] shared_secrets; private int fallback_count; private final boolean fallback_allowed; private boolean is_socks; private volatile PluginProxy plugin_proxy; /** * Constructor for disconnected (outbound) transport. */ public TCPTransportImpl( ProtocolEndpointTCP endpoint, boolean use_crypto, boolean allow_fallback, byte[][] _shared_secrets ) { protocol_endpoint = endpoint; is_inbound_connection = false; connect_with_crypto = use_crypto; shared_secrets = _shared_secrets; fallback_allowed = allow_fallback; } /** * Constructor for connected (inbound) transport. * @param channel connection * @param already_read bytes from the channel */ public TCPTransportImpl( ProtocolEndpointTCP endpoint, TransportHelperFilter filter ) { protocol_endpoint = endpoint; setFilter( filter ); is_inbound_connection = true; connect_with_crypto = false; //inbound connections will automatically be using crypto if necessary fallback_allowed = false; InetSocketAddress address = endpoint.getAddress(); description = ( is_inbound_connection ? "R" : "L" ) + ": " + address.getHostName() + ": " + address.getPort(); } /** * Get the socket channel used by the transport. * @return the socket channel */ public SocketChannel getSocketChannel() { TransportHelperFilter filter = getFilter(); if (filter == null) { return null; } TCPTransportHelper helper = (TCPTransportHelper)filter.getHelper(); if (helper == null) { return null; } return helper.getSocketChannel(); } public TransportEndpointTCP getTransportEndpoint() { return( new TransportEndpointTCP( protocol_endpoint, getSocketChannel())); } public TransportStartpoint getTransportStartpoint() { return( new TransportStartpointTCP( getTransportEndpoint())); } public int getMssSize() { return( TCPNetworkManager.getTcpMssSize()); } public boolean isTCP() { return( true ); } public boolean isSOCKS() { return( is_socks ); } public String getProtocol() { if ( is_socks ){ return( "TCP (SOCKS)" ); }else{ return "TCP"; } } /** * Get a textual description for this transport. * @return description */ public String getDescription() { return description; } /** * Request the transport connection be established. * NOTE: Will automatically connect via configured proxy if necessary. * @param address remote peer address to connect to * @param listener establishment failure/success listener */ public void connectOutbound( final ByteBuffer initial_data, final ConnectListener listener, final int priority ) { if ( !TCPNetworkManager.TCP_OUTGOING_ENABLED ){ listener.connectFailure( new Throwable( "Outbound TCP connections disabled" )); return; } if( has_been_closed ){ listener.connectFailure( new Throwable( "Connection already closed" )); return; } if( getFilter() != null ) { //already connected Debug.out( "socket_channel != null" ); listener.connectSuccess( this, initial_data ); return; } final InetSocketAddress address = protocol_endpoint.getAddress(); if ( !address.equals( ProxyLoginHandler.DEFAULT_SOCKS_SERVER_ADDRESS )){ is_socks = COConfigurationManager.getBooleanParameter( "Proxy.Data.Enable" ); if ( !is_socks ){ // see if a plugin can handle this connection if ( address.isUnresolved()){ String host = address.getHostName(); if ( AENetworkClassifier.categoriseAddress( host ) != AENetworkClassifier.AT_PUBLIC ){ Map<String,Object> opts = new HashMap<String,Object>(); Object peer_nets = listener.getConnectionProperty( AEProxyFactory.PO_PEER_NETWORKS ); if ( peer_nets != null ){ opts.put( AEProxyFactory.PO_PEER_NETWORKS, peer_nets ); } plugin_proxy = AEProxyFactory.getPluginProxy( "outbound connection", host, address.getPort(), opts ); } } } } final TCPTransportImpl transport_instance = this; TCPConnectionManager.ConnectListener connect_listener = new TCPConnectionManager.ConnectListener() { public int connectAttemptStarted( int default_connect_timeout ) { return( listener.connectAttemptStarted( default_connect_timeout )); } public void connectSuccess( final SocketChannel channel ) { if( channel == null ) { String msg = "connectSuccess:: given channel == null"; Debug.out( msg ); setConnectResult( false ); listener.connectFailure( new Exception( msg ) ); return; } if( has_been_closed ) { //closed between select ops TCPNetworkManager.getSingleton().getConnectDisconnectManager().closeConnection( channel ); //just close it setConnectResult( false ); listener.connectFailure( new Throwable( "Connection has been closed" )); return; } connect_request_key = null; description = ( is_inbound_connection ? "R" : "L" ) + ": " + channel.socket().getInetAddress().getHostAddress() + ": " + channel.socket().getPort(); if ( is_socks ){ //proxy server connection established, login if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID,"Socket connection established to proxy server [" +description+ "], login initiated...")); // set up a transparent filter for socks negotiation setFilter( TCPTransportHelperFilterFactory.createTransparentFilter( channel )); new ProxyLoginHandler( transport_instance, address, new ProxyLoginHandler.ProxyListener() { public void connectSuccess() { if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, "Proxy [" +description+ "] login successful." )); handleCrypto( address, channel, initial_data, priority, listener ); } public void connectFailure( Throwable failure_msg ) { close( "Proxy login failed" ); listener.connectFailure( failure_msg ); } }); }else if ( plugin_proxy != null ){ if (Logger.isEnabled()){ Logger.log(new LogEvent(LOGID,"Socket connection established via plugin proxy [" +description+ "], login initiated...")); } // set up a transparent filter for socks negotiation setFilter( TCPTransportHelperFilterFactory.createTransparentFilter( channel )); new ProxyLoginHandler( transport_instance, new InetSocketAddress( plugin_proxy.getHost(), plugin_proxy.getPort()), new ProxyLoginHandler.ProxyListener() { public void connectSuccess() { if (Logger.isEnabled()){ Logger.log(new LogEvent(LOGID, "Proxy [" +description+ "] login successful." )); } setConnectResult( true ); handleCrypto( address, channel, initial_data, priority, listener ); } public void connectFailure( Throwable failure_msg ) { setConnectResult( false ); close( "Proxy login failed" ); listener.connectFailure( failure_msg ); } }, "V4a", "", "" ); }else { //direct connection established, notify handleCrypto( address, channel, initial_data, priority, listener ); } } public void connectFailure( Throwable failure_msg ) { connect_request_key = null; setConnectResult( false ); listener.connectFailure( failure_msg ); } }; connect_request_key = connect_listener; InetSocketAddress to_connect; if ( is_socks ){ to_connect = ProxyLoginHandler.getProxyAddress( address ); }else if ( plugin_proxy != null ){ to_connect = (InetSocketAddress)plugin_proxy.getProxy().address(); }else{ to_connect = address; } TCPNetworkManager.getSingleton().getConnectDisconnectManager().requestNewConnection( to_connect, connect_listener, priority ); } protected void handleCrypto( final InetSocketAddress address, final SocketChannel channel, final ByteBuffer initial_data, final int priority, final ConnectListener listener ) { if( connect_with_crypto ) { //attempt encrypted transport final TransportHelper helper = new TCPTransportHelper( channel ); TransportCryptoManager.getSingleton().manageCrypto( helper, shared_secrets, false, initial_data, new TransportCryptoManager.HandshakeListener() { public void handshakeSuccess( ProtocolDecoder decoder, ByteBuffer remaining_initial_data ) { //System.out.println( description+ " | crypto handshake success [" +_filter.getName()+ "]" ); TransportHelperFilter filter = decoder.getFilter(); setFilter( filter ); if ( Logger.isEnabled()){ Logger.log(new LogEvent(LOGID, "Outgoing TCP stream to " + channel.socket().getRemoteSocketAddress() + " established, type = " + filter.getName(false))); } connectedOutbound( remaining_initial_data, listener ); } public void handshakeFailure( Throwable failure_msg ) { if( fallback_allowed && NetworkManager.OUTGOING_HANDSHAKE_FALLBACK_ALLOWED && !has_been_closed ) { if( Logger.isEnabled() ) Logger.log(new LogEvent(LOGID, description+ " | crypto handshake failure [" +failure_msg.getMessage()+ "], attempting non-crypto fallback." )); connect_with_crypto = false; fallback_count++; close( helper, "Handshake failure and retry" ); has_been_closed = false; if ( initial_data != null ){ initial_data.position( 0 ); } connectOutbound( initial_data, listener, priority ); } else { close( helper, "Handshake failure" ); listener.connectFailure( failure_msg ); } } public void gotSecret( byte[] session_secret ) { } public int getMaximumPlainHeaderLength() { throw( new RuntimeException()); // this is outgoing } public int matchPlainHeader( ByteBuffer buffer ) { throw( new RuntimeException()); // this is outgoing } }); } else { //no crypto //if( fallback_count > 0 ) { // System.out.println( channel.socket()+ " | non-crypto fallback successful!" ); //} setFilter( TCPTransportHelperFilterFactory.createTransparentFilter( channel )); if ( Logger.isEnabled()){ Logger.log(new LogEvent(LOGID, "Outgoing TCP stream to " + channel.socket().getRemoteSocketAddress() + " established, type = " + getFilter().getName(false) + ", fallback = " + (fallback_count==0?"no":"yes" ))); } connectedOutbound( initial_data, listener ); } } private void setTransportBuffersSize( int size_in_bytes ) { if( getFilter() == null ) { Debug.out( "socket_channel == null" ); return; } try{ SocketChannel channel = getSocketChannel(); channel.socket().setSendBufferSize( size_in_bytes ); channel.socket().setReceiveBufferSize( size_in_bytes ); int snd_real = channel.socket().getSendBufferSize(); int rcv_real = channel.socket().getReceiveBufferSize(); if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, "Setting new transport [" + description + "] buffer sizes: SND=" + size_in_bytes + " [" + snd_real + "] , RCV=" + size_in_bytes + " [" + rcv_real + "]")); } catch( Throwable t ) { Debug.out( t ); } } /** * Set the transport to the given speed mode. * @param mode to change to */ public void setTransportMode( int mode ) { if( mode == transport_mode ) return; //already in mode switch( mode ) { case TRANSPORT_MODE_NORMAL: setTransportBuffersSize( 8 * 1024 ); break; case TRANSPORT_MODE_FAST: setTransportBuffersSize( 64 * 1024 ); break; case TRANSPORT_MODE_TURBO: setTransportBuffersSize( 512 * 1024 ); break; default: Debug.out( "invalid transport mode given: " +mode ); } transport_mode = mode; } protected void connectedOutbound( ByteBuffer remaining_initial_data, ConnectListener listener ) { if ( has_been_closed ){ TransportHelperFilter filter = getFilter(); if ( filter != null ){ filter.getHelper().close( "Connection closed" ); setFilter( null ); } listener.connectFailure( new Throwable( "Connection closed" )); }else{ connectedOutbound(); listener.connectSuccess( this, remaining_initial_data ); } } /** * Get the transport's speed mode. * @return current mode */ public int getTransportMode() { return transport_mode; } protected void close( TransportHelper helper, String reason ) { helper.close( reason ); close( reason ); } private void setConnectResult( boolean ok ) { PluginProxy pp = plugin_proxy; if ( pp != null ){ plugin_proxy = null; pp.setOK(ok); } } /** * Close the transport connection. */ public void close( String reason ) { has_been_closed = true; setConnectResult( false ); if( connect_request_key != null ) { TCPNetworkManager.getSingleton().getConnectDisconnectManager().cancelRequest( connect_request_key ); } readyForRead( false ); readyForWrite( false ); TransportHelperFilter filter = getFilter(); if ( filter != null ){ filter.getHelper().close( reason ); setFilter( null ); } // we need to set it ready for reading so that the other scheduling thread wakes up // and discovers that this connection has been closed setReadyForRead(); } }