/* * Created on 13-Dec-2004 * Created by Paul Gardner * Copyright (C) 2004 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, SARL au capital de 30,000 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package com.aelitis.azureus.plugins.networks.i2p; import java.io.*; import java.net.InetAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import org.gudy.azureus2.core3.util.AEMonitor; import org.gudy.azureus2.core3.util.AERunnable; import org.gudy.azureus2.core3.util.AESemaphore; import org.gudy.azureus2.core3.util.AEThread; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.ThreadPool; import com.aelitis.azureus.core.proxy.*; import com.aelitis.azureus.core.proxy.socks.*; /** * @author parg * */ public class I2PPluginConnection implements AESocksProxyPlugableConnection { private static final boolean TRACE = true; // try to buffer at least a whole block public static final int RELAY_BUFFER_SIZE = 64*1024 + 256; protected I2PPluginConnectionManager con_man; protected Object socket; protected boolean socket_closed; protected AESocksProxyConnection proxy_connection; protected proxyStateRelayData relay_state; protected AEMonitor this_mon = new AEMonitor( "I2PPluginConnection" ); protected I2PPluginConnection( I2PPluginConnectionManager _con_man, AESocksProxyConnection _proxy_connection ) { con_man = _con_man; proxy_connection = _proxy_connection; proxy_connection.disableDNSLookups(); } public String getName() { return( "I2PPluginConnection" ); } public InetAddress getLocalAddress() { try{ return( InetAddress.getLocalHost() ); }catch( Throwable e ){ Debug.printStackTrace(e); return( null ); } } public int getLocalPort() { return( -1 ); } public void connect( AESocksProxyAddress _address ) throws IOException { if ( TRACE ){ con_man.log( "connect request to " + _address.getUnresolvedAddress() + "/" + _address.getAddress() + "/" + _address.getPort()); } if ( _address.getAddress() != null ){ if ( con_man.proxyI2POnly()){ if ( TRACE ){ con_man.log( " NOT delegating resolved, non-I2P delegation disabled" ); } throw( new IOException( "delegation to non-I2P locations disabled" )); } if ( TRACE ){ con_man.log( " delegating resolved" ); } AESocksProxyPlugableConnection delegate = proxy_connection.getProxy().getDefaultPlugableConnection( proxy_connection ); proxy_connection.setDelegate( delegate ); delegate.connect( _address ); }else{ final String externalised_address = AEProxyFactory.getAddressMapper().externalise(_address.getUnresolvedAddress()); if ( !externalised_address.toLowerCase().endsWith(".i2p")){ if ( con_man.proxyI2POnly()){ if ( TRACE ){ con_man.log( " NOT delegating unresolved, non-I2P delegation disabled" ); } throw( new IOException( "delegation to non-I2P locations disabled" )); } if ( TRACE ){ con_man.log( " delegating unresolved" ); } AESocksProxyPlugableConnection delegate = proxy_connection.getProxy().getDefaultPlugableConnection( proxy_connection ); proxy_connection.enableDNSLookups(); proxy_connection.setDelegate( delegate ); delegate.connect( _address ); }else{ AEThread connect_thread = new AEThread( "I2PConnect") { public void runSupport() { if ( TRACE ){ con_man.log( " delegating to I2P" ); } try{ // remove the .i2p String new_externalised_address = externalised_address.substring( 0, externalised_address.length() - 4 ); socket = con_man.i2pSocketManager_connect( new_externalised_address ); proxy_connection.connected(); }catch( Throwable e ){ try{ proxy_connection.close(); }catch( Throwable f ){ f.printStackTrace(); } e.printStackTrace(); con_man.log( "I2PSocket creation fails: " + Debug.getNestedExceptionMessage(e) ); } } }; connect_thread.setDaemon( true ); connect_thread.start(); } } } public void relayData() throws IOException { try{ this_mon.enter(); if ( socket_closed ){ throw( new IOException( "I2PPluginConnection::relayData: socket already closed")); } relay_state = new proxyStateRelayData( proxy_connection.getConnection()); }finally{ this_mon.exit(); } } public void close() throws IOException { try{ this_mon.enter(); if ( socket != null && !socket_closed ){ socket_closed = true; if ( relay_state != null ){ relay_state.close(); } final Object f_socket = socket; socket = null; AEThread t = new AEThread( "I2P SocketCloser" ) { public void runSupport() { try{ con_man.i2pSocket_close( f_socket ); }catch( Throwable e ){ } } }; t.setDaemon(true); t.start(); } }finally{ this_mon.exit(); } } protected class proxyStateRelayData implements AEProxyState { protected AEProxyConnection connection; protected ByteBuffer source_buffer; protected ByteBuffer target_buffer; protected SocketChannel source_channel; protected InputStream input_stream; protected OutputStream output_stream; protected long outward_bytes = 0; protected long inward_bytes = 0; protected AESemaphore write_sem = new AESemaphore( "I2PSocket write sem" ); protected ThreadPool async_pool = new ThreadPool( "I2PSocket async", 2 ); protected proxyStateRelayData( AEProxyConnection _connection ) throws IOException { connection = _connection; source_channel = connection.getSourceChannel(); source_buffer = ByteBuffer.allocate( RELAY_BUFFER_SIZE ); connection.setReadState( this ); connection.setWriteState( this ); connection.requestReadSelect( source_channel ); connection.setConnected(); input_stream = con_man.i2pSocket_getInputStream( socket ); output_stream = con_man.i2pSocket_getOutputStream( socket ); async_pool.run( new AERunnable() { public void runSupport() { byte[] buffer = new byte[RELAY_BUFFER_SIZE]; while( !connection.isClosed()){ try{ con_man.trace( "I2PCon: " + getStateName() + " : read Starts <- I2P " ); long start = System.currentTimeMillis(); int len = input_stream.read( buffer ); if ( len <= 0 ){ break; } con_man.trace( "I2PCon: " + getStateName() + " : read Done <- I2P - " + len + ", elapsed = " + ( System.currentTimeMillis() - start )); if ( target_buffer != null ){ Debug.out("I2PluginConnection: target buffer should be null" ); } target_buffer = ByteBuffer.wrap( buffer, 0, len ); read(); }catch( Throwable e ){ if ( e instanceof IOException && e.getMessage() != null && e.getMessage().startsWith( "Already closed" )){ // ignore this one }else{ Debug.printStackTrace(e); } break; } } if ( !proxy_connection.isClosed()){ try{ proxy_connection.close(); }catch( IOException e ){ Debug.printStackTrace(e); } } } }); } protected void close() { con_man.trace( "I2PCon: " + getStateName() + " close" ); write_sem.releaseForever(); } protected void read() throws IOException { // data from I2P connection.setTimeStamp(); int written = source_channel.write( target_buffer ); con_man.trace( "I2PCon: " + getStateName() + " : write -> AZ - " + written ); inward_bytes += written; if ( target_buffer.hasRemaining()){ connection.requestWriteSelect( source_channel ); write_sem.reserve(); } target_buffer = null; } public boolean read( SocketChannel sc ) throws IOException { if ( source_buffer.position() != 0 ){ Debug.out( "I2PluginConnection: source buffer position invalid" ); } // data read from source connection.setTimeStamp(); final int len = sc.read( source_buffer ); if ( len == 0 ){ return( false ); } if ( len == -1 ){ throw( new IOException( "read channel shutdown" )); }else{ if ( source_buffer.position() > 0 ){ connection.cancelReadSelect( source_channel ); con_man.trace( "I2PCon: " + getStateName() + " : read <- AZ - " + len ); // offload the write to separate thread as can't afford to block the // proxy async_pool.run( new AERunnable() { public void runSupport() { try{ source_buffer.flip(); long start = System.currentTimeMillis(); con_man.trace( "I2PCon: " + getStateName() + " : write Starts -> I2P - " + len ); output_stream.write( source_buffer.array(), 0, len ); source_buffer.position( 0 ); source_buffer.limit( source_buffer.capacity()); // output_stream.flush(); con_man.trace( "I2PCon: " + getStateName() + " : write done -> I2P - " + len + ", elapsed = " + ( System.currentTimeMillis() - start )); outward_bytes += len; connection.requestReadSelect( source_channel ); }catch( Throwable e ){ connection.failed( e ); } } }); } } return( true ); } public boolean write( SocketChannel sc ) throws IOException { try{ int written = source_channel.write( target_buffer ); inward_bytes += written; con_man.trace( "I2PCon: " + getStateName() + " write -> AZ: " + written ); if ( target_buffer.hasRemaining()){ connection.requestWriteSelect( source_channel ); }else{ write_sem.release(); } return( written > 0 ); }catch( Throwable e ){ write_sem.release(); if (e instanceof IOException ){ throw((IOException)e); } throw( new IOException( "write fails: " + Debug.getNestedExceptionMessage(e))); } } public boolean connect( SocketChannel sc ) throws IOException { throw( new IOException( "unexpected connect" )); } public String getStateName() { String state = this.getClass().getName(); int pos = state.indexOf( "$"); state = state.substring(pos+1); return( state +" [out=" + outward_bytes +",in=" + inward_bytes +"] " + (source_buffer==null?"":source_buffer.toString()) + " / " + target_buffer ); } } }