/* * Created on Aug 28, 2010 * Created by Paul Gardner * * Copyright 2010 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.core.networkmanager.impl.utp; import java.util.*; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import org.gudy.azureus2.core3.logging.LogEvent; import org.gudy.azureus2.core3.logging.LogIDs; import org.gudy.azureus2.core3.logging.Logger; import org.gudy.azureus2.core3.util.AERunnable; import org.gudy.azureus2.core3.util.AESemaphore; import org.gudy.azureus2.core3.util.AsyncDispatcher; import org.gudy.azureus2.core3.util.ByteFormatter; import org.gudy.azureus2.core3.util.Constants; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.SystemTime; import org.gudy.azureus2.plugins.PluginInterface; import com.aelitis.azureus.core.networkmanager.ConnectionEndpoint; import com.aelitis.azureus.core.networkmanager.ProtocolEndpoint; import com.aelitis.azureus.core.networkmanager.ProtocolEndpointFactory; import com.aelitis.azureus.core.networkmanager.impl.IncomingConnectionManager; import com.aelitis.azureus.core.networkmanager.impl.ProtocolDecoder; import com.aelitis.azureus.core.networkmanager.impl.TransportCryptoManager; import com.aelitis.azureus.core.networkmanager.impl.TransportHelperFilter; import com.aelitis.net.udp.uc.PRUDPPacketHandler; import com.vuze.client.plugins.utp.UTPPlugin; import com.vuze.client.plugins.utp.UTPProvider; import com.vuze.client.plugins.utp.UTPProviderCallback; import com.vuze.client.plugins.utp.UTPProviderFactory; public class UTPConnectionManager { private static final int MIN_MSS = 256; private static final int MAX_HEADER = 128; public static final int MIN_WRITE_PAYLOAD = MIN_MSS - MAX_HEADER; public static final int MAX_BUFFERED_PAYLOAD = 512; private static final int CLOSING_TIMOUT = 2*60*1000; private static final int UTP_PROVIDER_TIMEOUT = 30*1000; private static final LogIDs LOGID = LogIDs.NET; private boolean initialised; private UTPPlugin plugin; private PRUDPPacketHandler packet_handler; private int local_port; private IncomingConnectionManager incoming_manager = IncomingConnectionManager.getSingleton(); private AsyncDispatcher dispatcher = new AsyncDispatcher(); private UTPSelector selector; private List<UTPConnection> connections = new ArrayList<UTPConnection>(); private Map<InetAddress,List<UTPConnection>> address_connection_map = new HashMap<InetAddress, List<UTPConnection>>(); private Map<Long,UTPConnection> socket_connection_map = new HashMap<Long, UTPConnection>(); private Set<UTPConnection> closing_connections = new HashSet<UTPConnection>(); // provider version 1 only private UTPConnection active_write; private ByteBuffer[] active_write_buffers; private int active_write_start; private int active_write_len; private static final long MAX_INCOMING_QUEUED = 4*1024*1024; private static final long MAX_INCOMING_QUEUED_LOG_OK = MAX_INCOMING_QUEUED - 256*1024; public static final int DEFAULT_RECV_BUFFER_KB = UTPProvider.DEFAULT_RECV_BUFFER_KB; public static final int DEFAULT_SEND_BUFFER_KB = UTPProvider.DEFAULT_SEND_BUFFER_KB; private long total_incoming_queued; private int total_incoming_queued_log_state; private boolean available; private boolean hack_worked; private long last_hack_attempt; private Object last_hack; private boolean prefer_utp; private UTPProvider utp_provider = UTPProviderFactory.createProvider(); private volatile AESemaphore poll_waiter; public UTPConnectionManager( UTPPlugin _plugin ) { plugin = _plugin; dispatcher.setPriority( Thread.MAX_PRIORITY - 1 ); } public int getProviderVersion() { return( utp_provider.getVersion()); } public void activate( PRUDPPacketHandler _handler ) { packet_handler = _handler; local_port = packet_handler.getPort(); synchronized( this){ if ( initialised ){ return; } initialised = true; } final AESemaphore init_sem = new AESemaphore( "uTP:init" ); PluginInterface pi = plugin.getPluginInterface(); final File plugin_user_dir = pi.getPluginconfig().getPluginUserFile( "plugin.properties" ).getParentFile(); File plugin_install_dir = new File( pi.getPluginDirectoryName()); if ( plugin_install_dir == null || !plugin_install_dir.exists()){ plugin_install_dir = plugin_user_dir; } final File f_plugin_install_dir = plugin_install_dir; try{ available = utp_provider.load( new UTPProviderCallback() { public File getPluginUserDir() { return( plugin_user_dir ); } public File getPluginInstallDir() { return( f_plugin_install_dir ); } public void log( String str, Throwable error ) { plugin.log(str,error); } public int getRandom() { return( UTPUtils.UTP_Random()); } public long getMilliseconds() { return( UTPUtils.UTP_GetMilliseconds()); } public long getMicroseconds() { return( UTPUtils.UTP_GetMicroseconds()); } public void incomingConnection( String host, int port, long utp_socket, long con_id ) { init_sem.reserve(); accept( new InetSocketAddress( host, port), utp_socket, con_id ); } public void incomingConnection( InetSocketAddress adress, long utp_socket, long con_id ) { init_sem.reserve(); accept( adress, utp_socket, con_id ); } public boolean send( String address, int port, byte[] buffer, int length ) { return( plugin.send( new InetSocketAddress( address, port ), buffer, length )); } public boolean send( InetSocketAddress adress, byte[] buffer, int length ) { return( plugin.send( adress, buffer, length )); } public void read( long utp_socket, byte[] data ) { UTPConnection connection; synchronized( UTPConnectionManager.this ){ connection = socket_connection_map.get( utp_socket ); } if ( connection == null ){ Debug.out( "read: unknown socket!" ); }else{ try{ connection.receive( ByteBuffer.wrap( data )); }catch( Throwable e ){ connection.close( Debug.getNestedExceptionMessage(e)); } } } public void read( long utp_socket, ByteBuffer bb ) { UTPConnection connection; synchronized( UTPConnectionManager.this ){ connection = socket_connection_map.get( utp_socket ); } if ( connection == null ){ Debug.out( "read: unknown socket!" ); }else{ try{ connection.receive( bb ); }catch( Throwable e ){ connection.close( Debug.getNestedExceptionMessage(e)); } } } public void write( long utp_socket, byte[] data, int offset, int length ) { UTPConnection connection; synchronized( UTPConnectionManager.this ){ connection = socket_connection_map.get( utp_socket ); } if ( connection == null ){ Debug.out( "write: unknown socket!" ); }else{ try{ if ( utp_provider.getVersion() != 1 ){ throw( new Exception( "Invalid flow control" )); } if ( active_write != connection ){ throw( new Exception( "Write for incorrect connection!" )); } int pos = offset; int rem = length; for ( int i=active_write_start; i<active_write_start+active_write_len && rem > 0 ;i++){ ByteBuffer b = active_write_buffers[i]; int remaining = b.remaining(); if ( remaining > 0 ){ int to_read = Math.min( rem, remaining ); b.get( data, pos, to_read ); pos += to_read; rem -= to_read; } } if ( rem != 0 ){ throw( new Exception( "insufficient data available for write operation" )); } }catch( Throwable e ){ connection.close( Debug.getNestedExceptionMessage(e)); } } } public int getReadBufferSize( long utp_socket ) { UTPConnection connection; synchronized( UTPConnectionManager.this ){ connection = socket_connection_map.get( utp_socket ); } if ( connection == null ){ // can get this during socket shutdown return( 0 ); }else{ int res = connection.getReceivePendingSize(); // we lie here if we have a fair bit queued as this allows // us to control the receive window if ( res > 512*1024 ){ // forces us to advertize a window of 0 bytes // to prevent peer from sending us mroe data until // we've managed to flush this to disk res = Integer.MAX_VALUE; } return( res ); } } public void setState( long utp_socket, int state ) { UTPConnection connection; synchronized( UTPConnectionManager.this ){ connection = socket_connection_map.get( utp_socket ); } if ( connection == null ){ // can get this during socket shutdown }else{ if ( state == STATE_CONNECT ){ connection.setConnected(); } if ( state == STATE_CONNECT || state == STATE_WRITABLE ){ connection.setCanWrite( true ); }else if ( state == STATE_EOF ){ connection.close( "EOF" ); }else if ( state == STATE_DESTROYING ){ connection.setUnusable(); connection.close( "Connection destroyed" ); if ( closing_connections.remove( connection )){ removeConnection( connection ); } } } } public void error( long utp_socket, int error ) { UTPConnection connection; synchronized( UTPConnectionManager.this ){ connection = socket_connection_map.get( utp_socket ); } if ( connection == null ){ // can get this during socket shutdown }else{ connection.close( "Socket error: code=" + error ); } } public void overhead( long utp_socket, boolean send, int size, int type ) { // System.out.println( "overhead( " + send + "," + size + "," + type + " )" ); } }); if ( available ){ hackHandler(); selector = new UTPSelector( this ); ProtocolEndpointUTP.register( this ); } }finally{ init_sem.releaseForever(); } } public void deactivate() { // TODO: } public UTPConnection connect( final InetSocketAddress target, final UTPTransportHelper transport ) throws IOException { if ( target.isUnresolved()){ throw( new UnknownHostException( target.getHostName())); } final Object[] result = { null }; final AESemaphore sem = new AESemaphore( "uTP:connect" ); dispatcher.dispatch( new AERunnable() { public void runSupport() { try{ long[] x = utp_provider.connect( target.getAddress().getHostAddress(), target.getPort()); if ( x != null ){ result[0] = addConnection( target, transport, x[0], x[1] ); }else{ result[0] = new IOException( "Connect failed" ); } }catch( Throwable e ){ e.printStackTrace(); result[0] = new IOException( "Connect failed: " + Debug.getNestedExceptionMessage(e)); }finally{ sem.release(); } } }); if ( !sem.reserve( UTP_PROVIDER_TIMEOUT )){ Debug.out( "Deadlock probably detected" ); throw( new IOException( "Deadlock" )); } if ( result[0] instanceof UTPConnection ){ return((UTPConnection)result[0]); }else{ throw((IOException)result[0]); } } public boolean receive( InetSocketAddress from, byte[] data, int length ) { if ( !available ){ return( false ); } InetAddress address = from.getAddress(); if ( address instanceof Inet4Address ){ if ( length >= 20 ){ byte first_byte = data[0]; // System.out.println( "UDP: " + ByteFormatter.encodeString( data, 0, length ) + " from " + from + " - " + new String( data, 0, length )); if ( first_byte == 0x41 && // SYN + version 1 data[8] == 0 && data[9] == 0 && data[10] == 0 && data[11] == 0 && // time diff = 0 // data[16] == 0 && data[17] == 1 ){ // seq = 1 data[18] == 0 && data[19] == 0 ){ // ack = 0 /* 4102C5F60499238B00000000003800000001000000080000000000000000 4102 CDF2 // SYN, ver 1, ext 2, con id CDF2 6A39693A // usec 00000000 // rep micro 00380000 wnd = 3.5MB 00010000 seq = 1, ack = 0 00080000 ext len = 8, no more ext 00000000 0000 */ // then modified to use random initial sequence number // 4102e5331fb2e61900000000003800003aee000000080000000000000000 // System.out.println( "Looks like uTP incoming connection from " + from ); return( doReceive( address.getHostAddress(), from.getPort(), data, length )); }else if ( (first_byte&0x0f)==0x01 ){ /* 0100B5621AE099301AD4C472003800000002482213426974546F7272656E742070726F746F636F6C0000000000100005A 0100 // (x+1) + ext type B562 // con id 1AE09930 // usec 1AD4C472 // rep micro 00380000 // recv win bytes 0002 // seq 4822 // ack 13426974546F7272656E742070726F746F636F6C0000000000100005A */ // 210063CB1EFC51C01BA91F010003200036B56BFD int type = (data[0]>>>4)&0x0f; if ( type >= 0 && type <= 4 ){ int con_id = ((data[2]<<8)&0xff00) | (data[3]&0x00ff); UTPConnection connection = null; synchronized( this ){ List<UTPConnection> l = address_connection_map.get( address ); if ( l != null ){ for ( UTPConnection c:l ){ if ( c.getConnectionID() == con_id ){ connection = c; break; } } } /* if ( connection == null ){ String existing = ""; for ( Map.Entry<InetAddress, List<UTPConnection>> entry: address_connection_map.entrySet()){ String str = entry.getKey() + "->"; for (UTPConnection u: entry.getValue()){ str += u.getConnectionID() + ","; } existing += str + " "; } System.out.println( "Connection not found for " + from + "/" + con_id + ": " + existing ); } */ } if ( connection != null ){ // System.out.println( "Looks like uTP incoming data from " + from ); return( doReceive( address.getHostAddress(), from.getPort(), data, length )); }else{ // System.out.println( "No match from " + from + ": " + ByteFormatter.encodeString( data, 0, length )); } } } } } return( false ); } private boolean doReceive( final String from_address, final int from_port, final byte[] data, final int length ) { if ( !utp_provider.isValidPacket( data, length )){ return( false ); } synchronized( this ){ if ( total_incoming_queued > MAX_INCOMING_QUEUED ){ if ( total_incoming_queued_log_state == 0 ){ Debug.out( "uTP pending packet queue too large, discarding..." ); total_incoming_queued_log_state = 1; } return( true ); } if ( total_incoming_queued_log_state == 1 ){ if ( total_incoming_queued < MAX_INCOMING_QUEUED_LOG_OK ){ Debug.out( "uTP pending packet queue emptied, processing..." ); total_incoming_queued_log_state = 0; } } total_incoming_queued += length; } dispatcher.dispatch( new AERunnable() { public void runSupport() { synchronized( UTPConnectionManager.this ){ total_incoming_queued -= length; } //System.out.println( "recv " + from_address + ":" + from_port + " - " + ByteFormatter.encodeString( data, 0, length )); try{ if ( !utp_provider.receive( from_address, from_port, data, length )){ if ( Constants.IS_CVS_VERSION ){ Debug.out( "Failed to process uTP packet: " + ByteFormatter.encodeString( data, 0, length ) + " from " + from_address); } } }catch( Throwable e ){ Debug.out( e ); } } }); return( true ); } private void accept( final InetSocketAddress remote_address, long utp_socket, long con_id ) { final UTPConnection new_connection = addConnection( remote_address, null, utp_socket, con_id ); final UTPTransportHelper helper = new UTPTransportHelper( this, remote_address, new_connection ); if ( !UTPNetworkManager.UTP_INCOMING_ENABLED ){ helper.close( "Incoming uTP connections disabled" ); return; } log( "Incoming connection from " + remote_address ); try{ new_connection.setTransport( helper ); TransportCryptoManager.getSingleton().manageCrypto( helper, null, true, null, new TransportCryptoManager.HandshakeListener() { public void handshakeSuccess( ProtocolDecoder decoder, ByteBuffer remaining_initial_data ) { TransportHelperFilter filter = decoder.getFilter(); ConnectionEndpoint co_ep = new ConnectionEndpoint( remote_address); ProtocolEndpointUTP pe_utp = (ProtocolEndpointUTP)ProtocolEndpointFactory.createEndpoint( ProtocolEndpoint.PROTOCOL_UTP, co_ep, remote_address ); UTPTransport transport = new UTPTransport( UTPConnectionManager.this, pe_utp, filter ); helper.setTransport( transport ); incoming_manager.addConnection( local_port, filter, transport ); log( "Connection established to " + helper.getAddress()); } public void handshakeFailure( Throwable failure_msg ) { if (Logger.isEnabled()){ Logger.log(new LogEvent(LOGID, "incoming crypto handshake failure: " + Debug.getNestedExceptionMessage( failure_msg ))); } log( "Failed to established connection to " + helper.getAddress() + ": " + Debug.getNestedExceptionMessage(failure_msg) ); new_connection.close( "handshake failure: " + Debug.getNestedExceptionMessage(failure_msg)); } public void gotSecret( byte[] session_secret ) { } public int getMaximumPlainHeaderLength() { return( incoming_manager.getMaxMinMatchBufferSize()); } public int matchPlainHeader( ByteBuffer buffer ) { Object[] match_data = incoming_manager.checkForMatch( helper, local_port, buffer, true ); if ( match_data == null ){ return( TransportCryptoManager.HandshakeListener.MATCH_NONE ); }else{ IncomingConnectionManager.MatchListener match = (IncomingConnectionManager.MatchListener)match_data[0]; if ( match.autoCryptoFallback()){ return( TransportCryptoManager.HandshakeListener.MATCH_CRYPTO_AUTO_FALLBACK ); }else{ return( TransportCryptoManager.HandshakeListener.MATCH_CRYPTO_NO_AUTO_FALLBACK ); } } } }); }catch( Throwable e ){ Debug.printStackTrace( e ); helper.close( Debug.getNestedExceptionMessage(e)); } } private UTPConnection addConnection( InetSocketAddress remote_address, UTPTransportHelper transport_helper, // null for incoming long utp_socket, long con_id ) { List<UTPConnection> to_destroy = null; final UTPConnection new_connection = new UTPConnection( this, remote_address, transport_helper, utp_socket, con_id ); synchronized( this ){ List<UTPConnection> l = address_connection_map.get( remote_address.getAddress()); if ( l != null ){ for ( UTPConnection c: l ){ if ( c.getConnectionID() == con_id ){ if ( to_destroy == null ){ to_destroy = new ArrayList<UTPConnection>(); } to_destroy.add( c ); l.remove( c ); connections.remove( c ); break; } } }else{ l = new ArrayList<UTPConnection>(); address_connection_map.put( remote_address.getAddress(), l ); } l.add( new_connection ); connections.add( new_connection ); UTPConnection existing = socket_connection_map.put( utp_socket, new_connection ); // System.out.println( "Add connection " + remote_address + ": total=" + connections.size() + "/" + address_connection_map.size() + "/" + socket_connection_map.size()); if ( existing != null ){ Debug.out( "Existing socket found at same address!!!!" ); if ( to_destroy == null ){ to_destroy = new ArrayList<UTPConnection>(); } to_destroy.add( existing ); } } if ( to_destroy != null ){ for ( UTPConnection c: to_destroy ){ c.close( "Connection replaced" ); } } AESemaphore sem = poll_waiter; if ( sem != null ){ poll_waiter = null; sem.release(); } return( new_connection ); } private void removeConnection( UTPConnection c ) { synchronized( this ){ connections.remove( c ); List<UTPConnection> l = address_connection_map.get( c.getRemoteAddress().getAddress()); if ( l != null ){ l.remove( c ); if ( l.size() == 0 ){ address_connection_map.remove( c.getRemoteAddress().getAddress()); } } UTPConnection existing = socket_connection_map.get( c.getSocket()); if ( existing == c ){ socket_connection_map.remove( c.getSocket()); } // System.out.println( "Remove connection: " + c.getRemoteAddress() + ": total=" + connections.size() + "/" + address_connection_map.size() + "/" + socket_connection_map.size()); } } protected UTPSelector getSelector() { return( selector ); } protected int poll( AESemaphore wait_sem, long now ) { if ( hack_worked && now - last_hack_attempt > 60*1000 ){ last_hack_attempt = now; hackHandler(); } dispatcher.dispatch( new AERunnable() { public void runSupport() { utp_provider.checkTimeouts(); if ( closing_connections.size() > 0 ){ long now = SystemTime.getMonotonousTime(); Iterator<UTPConnection> it = closing_connections.iterator(); while( it.hasNext()){ UTPConnection c = it.next(); long close_time = c.getCloseTime(); if ( close_time > 0 ){ if ( now - close_time > CLOSING_TIMOUT ){ it.remove(); removeConnection( c ); log( "Removing " + c.getString() + " due to close timeout" ); } } } } } }); int result = connections.size(); if ( result == 0 ){ poll_waiter = wait_sem; } return( result ); } protected int write( final UTPConnection c, final ByteBuffer[] buffers, final int start, final int len ) throws IOException { final AESemaphore sem = new AESemaphore( "uTP:write" ); final Object[] result = {null}; dispatcher.dispatch( new AERunnable() { public void runSupport() { boolean log_error = true; try{ if ( c.isUnusable()){ log_error = false; throw( new Exception( "Connection is closed" )); }else if ( !c.isConnected()){ log_error = false; throw( new Exception( "Connection is closed" )); }else if ( !c.canWrite()){ Debug.out( "Write operation on non-writable connection" ); result[0] = 0; }else{ if ( utp_provider.getVersion() == 1 ){ int pre_total = 0; for (int i=start;i<start+len;i++){ pre_total += buffers[i].remaining(); } try{ active_write = c; active_write_buffers = buffers; active_write_start = start; active_write_len = len; boolean still_writable = utp_provider.write( c.getSocket(), pre_total ); c.setCanWrite( still_writable ); }finally{ active_write = null; active_write_buffers = null; } int post_total = 0; for (int i=start;i<start+len;i++){ post_total += buffers[i].remaining(); } result[0] = pre_total - post_total; }else{ int pre_total = 0; for (int i=start;i<start+len;i++){ pre_total += buffers[i].remaining(); } boolean still_writable = utp_provider.write( c.getSocket(), buffers, start, len ); c.setCanWrite( still_writable ); int post_total = 0; for (int i=start;i<start+len;i++){ post_total += buffers[i].remaining(); } result[0] = pre_total - post_total; } } }catch( Throwable e ){ if ( log_error ){ Debug.out( e ); } c.close( Debug.getNestedExceptionMessage(e)); result[0] = new IOException( "Write failed: " + Debug.getNestedExceptionMessage(e)); }finally{ sem.release(); } } }); if ( !sem.reserve( UTP_PROVIDER_TIMEOUT )){ Debug.out( "Deadlock probably detected" ); throw( new IOException( "Deadlock" )); } if ( result[0] instanceof Integer ){ return((Integer)result[0]); } throw((IOException)result[0]); } private AERunnable inputIdleDispatcher = new AERunnable() { public void runSupport() { try{ utp_provider.incomingIdle(); }catch( Throwable e ){ Debug.out( e ); } } }; protected void inputIdle() { dispatcher.dispatch( inputIdleDispatcher ); } protected void readBufferDrained( final UTPConnection c ) { dispatcher.dispatch( new AERunnable() { public void runSupport() { if ( !c.isUnusable()){ try{ utp_provider.receiveBufferDrained( c.getSocket()); }catch( Throwable e ){ Debug.out( e ); } } } }); } protected void close( final UTPConnection c, final String r ) { dispatcher.dispatch( new AERunnable() { public void runSupport() { closeSupport( c, r ); } }); } private void closeSupport( UTPConnection c, String r ) { boolean async_close = false; try{ if ( !c.isUnusable()){ log( "Closed connection to " + c.getRemoteAddress() + ": " + r + " (" + c.getState() + ")" ); try{ c.setUnusable(); utp_provider.close( c.getSocket() ); // wait for the destroying callback async_close = true; }catch( Throwable e ){ Debug.out( e ); } } }finally{ if ( async_close ){ synchronized( closing_connections ){ closing_connections.add( c ); } }else{ synchronized( closing_connections ){ if ( closing_connections.contains( c )){ return; } } removeConnection( c ); } } } public void preferUTP( boolean b ) { prefer_utp = b; } protected boolean preferUTP() { return( prefer_utp ); } public void setReceiveBufferSize( int size ) { utp_provider.setOption( UTPProvider.OPT_RECEIVE_BUFFER, size==0?DEFAULT_RECV_BUFFER_KB:size ); } public void setSendBufferSize( int size ) { utp_provider.setOption( UTPProvider.OPT_SEND_BUFFER, size==0?DEFAULT_SEND_BUFFER_KB:size ); } protected void log( String str ) { plugin.log( str ); } private void hackHandler() { try{ Class cla = packet_handler.getClass(); Field f_socket = cla.getDeclaredField( "socket" ); f_socket.setAccessible( true ); Object dg_sock = f_socket.get( packet_handler ); if ( last_hack == dg_sock ){ return; } last_hack = dg_sock; Field f_impl = dg_sock.getClass().getDeclaredField( "impl" ); f_impl.setAccessible( true ); Object dg_impl = f_impl.get( dg_sock ); Class dg_class = dg_impl.getClass(); int hacked = 0; while( dg_class != null ){ String[] field_names = { "fd", "fd1", "fd2" }; for ( String field_name: field_names ){ try{ Field f_fd = dg_class.getDeclaredField( field_name ); f_fd.setAccessible( true ); Object fd = f_fd.get( dg_impl ); if ( fd != null ){ Field f_fd_fd = fd.getClass().getDeclaredField( "fd" ); f_fd_fd.setAccessible( true ); Object fd_fd = f_fd_fd.get( fd ); utp_provider.setSocketOptions(((Number)fd_fd).longValue()); hacked++; } }catch( Throwable e ){ } } dg_class = dg_class.getSuperclass(); } hack_worked = hacked > 0; log( "Set options on " + hacked + " socket(s)" ); }catch( Throwable e ){ hack_worked = false; log( "Failed to set socket options: " + Debug.getNestedExceptionMessage(e)); } } }