/* * Created on Jun 8, 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.net.udp.uc.impl; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.PasswordAuthentication; import java.net.Proxy; import java.net.Socket; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.config.ParameterListener; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.HostNameToIPResolver; import com.aelitis.azureus.core.proxy.AEProxyFactory; import com.aelitis.azureus.core.proxy.AEProxySelector; import com.aelitis.azureus.core.proxy.AEProxySelectorFactory; import com.aelitis.net.udp.uc.PRUDPPacket; import com.aelitis.net.udp.uc.PRUDPPacketHandler; import com.aelitis.net.udp.uc.PRUDPPacketHandlerException; import com.aelitis.net.udp.uc.PRUDPPacketHandlerStats; import com.aelitis.net.udp.uc.PRUDPPacketReceiver; import com.aelitis.net.udp.uc.PRUDPPrimordialHandler; import com.aelitis.net.udp.uc.PRUDPRequestHandler; public class PRUDPPacketHandlerSocks implements PRUDPPacketHandler, PRUDPPacketHandlerImpl.PacketTransformer { private static String socks_host; private static int socks_port; private static String socks_user; private static String socks_password; static{ COConfigurationManager.addAndFireParameterListeners( new String[]{ "Proxy.Host", "Proxy.Port", "Proxy.Username", "Proxy.Password", }, new ParameterListener() { public void parameterChanged( String parameter_name ) { socks_host = COConfigurationManager.getStringParameter("Proxy.Host").trim(); socks_port = Integer.parseInt(COConfigurationManager.getStringParameter("Proxy.Port").trim()); socks_user = COConfigurationManager.getStringParameter("Proxy.Username").trim(); socks_password = COConfigurationManager.getStringParameter("Proxy.Password").trim(); if ( socks_user.equalsIgnoreCase("<none>")){ socks_user = ""; } } }); } final private InetSocketAddress target; private Socket control_socket; private InetSocketAddress relay; private PRUDPPacketHandler delegate; private byte[] packet_out_header; protected PRUDPPacketHandlerSocks( InetSocketAddress _target ) throws PRUDPPacketHandlerException { target = _target; boolean ok = false; AEProxySelector proxy_selector = AEProxySelectorFactory.getSelector(); Proxy proxy = proxy_selector.getSOCKSProxy( socks_host, socks_port, target ); boolean proxy_connected = false; Throwable error = null; try{ delegate = new PRUDPPacketHandlerImpl( 0, null, this ); control_socket = new Socket( Proxy.NO_PROXY ); InetSocketAddress proxy_address = (InetSocketAddress)proxy.address(); control_socket.connect( proxy_address ); proxy_connected = true; DataOutputStream dos = new DataOutputStream( new BufferedOutputStream( control_socket.getOutputStream(), 256 )); DataInputStream dis = new DataInputStream( control_socket.getInputStream()); dos.writeByte( (byte)5 ); // socks 5 dos.writeByte( (byte)2 ); // 2 methods dos.writeByte( (byte)0 ); // no auth dos.writeByte( (byte)2 ); // user/pw dos.flush(); dis.readByte(); // version byte byte method = dis.readByte(); if ( method != 0 && method != 2 ){ throw new IOException( "SOCKS 5: no valid method [" + method + "]" ); } // auth if ( method == 2 ) { dos.writeByte( (byte)1 ); // user/pw version dos.writeByte( (byte)socks_user.length() ); // user length dos.write( socks_user.getBytes() ); dos.writeByte( (byte)socks_password.length() ); // password length dos.write( socks_password.getBytes() ); dos.flush(); dis.readByte(); // version byte byte status = dis.readByte(); if ( status != 0 ){ throw( new IOException( "SOCKS 5: authentication fails [status=" +status+ "]" )); } } String mapped_ip; if ( target.isUnresolved() || target.getAddress() == null ){ // deal with long "hostnames" that we get for, e.g., I2P destinations mapped_ip = AEProxyFactory.getAddressMapper().internalise( target.getHostName() ); }else{ mapped_ip = target.getAddress().getHostName(); } dos.writeByte( (byte)5 ); // version dos.writeByte( (byte)3 ); // udp associate dos.writeByte( (byte)0 ); // reserved dos.writeByte((byte)1); dos.write( new byte[4] ); dos.writeShort( (short)delegate.getPort()); // port dos.flush(); dis.readByte(); // ver byte reply = dis.readByte(); if ( reply != 0 ){ // special hack for internal socks servers just being used for plumbing connections // for other protocols - 0x45 means 'go transparent' if ( reply == 0x45 && proxy_address.getAddress().isLoopbackAddress()){ control_socket.close(); control_socket = null; ok = true; // relay is null here - this drives other direct behaviour return; } throw( new IOException( "SOCKS 5: udp association fails [reply=" +reply+ "]" )); } dis.readByte(); // reserved InetAddress relay_address; byte atype = dis.readByte(); if ( atype == 1 ){ byte[] bytes = new byte[4]; dis.readFully( bytes ); relay_address = InetAddress.getByAddress( bytes ); }else if ( atype == 3 ){ byte len = dis.readByte(); byte[] bytes = new byte[(int)len&0xff ]; dis.readFully( bytes ); relay_address = InetAddress.getByName( new String( bytes )); }else{ byte[] bytes = new byte[16]; dis.readFully( bytes ); relay_address = InetAddress.getByAddress( bytes ); } int relay_port = ((dis.readByte()<<8)&0xff00) | (dis.readByte() & 0x00ff ); if ( relay_address.isAnyLocalAddress()){ relay_address = control_socket.getInetAddress(); } relay = new InetSocketAddress( relay_address, relay_port ); // use the maped ip for dns resolution so we don't leak the // actual address if this is a secure one (e.g. I2P one) ByteArrayOutputStream baos_temp = new ByteArrayOutputStream(); DataOutputStream dos_temp = new DataOutputStream( baos_temp ); dos_temp.writeByte(0); // resv dos_temp.writeByte(0); // resv dos_temp.writeByte(0); // frag (none) try { byte[] ip_bytes = HostNameToIPResolver.syncResolve( mapped_ip ).getAddress(); dos_temp.writeByte( ip_bytes.length==4?(byte)1:(byte)4 ); dos_temp.write( ip_bytes ); }catch( Throwable e ){ dos_temp.writeByte( (byte)3 ); // address type = domain name dos_temp.writeByte( (byte)mapped_ip.length() ); // address type = domain name dos_temp.write( mapped_ip.getBytes() ); } dos_temp.writeShort( (short)target.getPort() ); // port dos_temp.flush(); packet_out_header = baos_temp.toByteArray(); ok = true; Thread.sleep(1000); }catch( Throwable e ){ error = e; throw( new PRUDPPacketHandlerException( "socks setup failed: " + Debug.getNestedExceptionMessage(e), e)); }finally{ if ( !proxy_connected ){ proxy_selector.connectFailed( proxy, error ); } if ( !ok ){ try{ control_socket.close(); }catch( Throwable e ){ Debug.out( e ); }finally{ control_socket = null; } if ( delegate != null ){ try{ delegate.destroy(); }finally{ delegate = null; } } } } } public void transformSend( DatagramPacket packet ) { if ( relay == null ){ return; } byte[] data = packet.getData(); int data_len = packet.getLength(); byte[] new_data = new byte[data_len+packet_out_header.length]; System.arraycopy( packet_out_header, 0, new_data, 0, packet_out_header.length ); System.arraycopy( data, 0, new_data, packet_out_header.length, data_len); packet.setData( new_data ); } public void transformReceive( DatagramPacket packet ) { if ( relay == null ){ return; } byte[] data = packet.getData(); int data_len = packet.getLength(); DataInputStream dis = new DataInputStream( new ByteArrayInputStream( data, 0, data_len )); try{ dis.readByte(); // res dis.readByte(); // res dis.readByte(); // assume no frag byte atype = dis.readByte(); int encap_len = 4; if ( atype == 1 ){ encap_len += 4; }else if ( atype == 3 ){ encap_len += 1 + (dis.readByte()&0xff); }else{ encap_len += 16; } encap_len += 2; // port byte[] new_data = new byte[data_len-encap_len]; System.arraycopy( data, encap_len, new_data, 0, data_len - encap_len ); packet.setData( new_data ); }catch( IOException e ){ Debug.out( e ); } } private void checkAddress( InetSocketAddress destination ) throws PRUDPPacketHandlerException { if ( !destination.equals( target )){ throw( new PRUDPPacketHandlerException( "Destination mismatch" )); } } public void sendAndReceive( PRUDPPacket request_packet, InetSocketAddress destination_address, PRUDPPacketReceiver receiver, long timeout, int priority ) throws PRUDPPacketHandlerException { checkAddress( destination_address ); if ( relay != null ){ destination_address = relay; } delegate.sendAndReceive( request_packet, destination_address, receiver, timeout, priority ); } public PRUDPPacket sendAndReceive( PasswordAuthentication auth, PRUDPPacket request_packet, InetSocketAddress destination_address ) throws PRUDPPacketHandlerException { checkAddress( destination_address ); if ( relay != null ){ destination_address = relay; } return( delegate.sendAndReceive( auth, request_packet, destination_address)); } public PRUDPPacket sendAndReceive( PasswordAuthentication auth, PRUDPPacket request_packet, InetSocketAddress destination_address, long timeout_millis ) throws PRUDPPacketHandlerException { checkAddress( destination_address ); if ( relay != null ){ destination_address = relay; } return( delegate.sendAndReceive(auth, request_packet, destination_address, timeout_millis )); } public PRUDPPacket sendAndReceive( PasswordAuthentication auth, PRUDPPacket request_packet, InetSocketAddress destination_address, long timeout_millis, int priority ) throws PRUDPPacketHandlerException { checkAddress( destination_address ); if ( relay != null ){ destination_address = relay; } return( delegate.sendAndReceive(auth, request_packet, destination_address, timeout_millis, priority )); } public void send( PRUDPPacket request_packet, InetSocketAddress destination_address ) throws PRUDPPacketHandlerException { checkAddress( destination_address ); if ( relay != null ){ destination_address = relay; } delegate.send( request_packet, destination_address ); } public PRUDPRequestHandler getRequestHandler() { return( delegate.getRequestHandler()); } public void setRequestHandler( PRUDPRequestHandler request_handler ) { delegate.setRequestHandler( request_handler ); } public void primordialSend( byte[] data, InetSocketAddress target ) throws PRUDPPacketHandlerException { throw( new PRUDPPacketHandlerException( "not imp" )); } public boolean hasPrimordialHandler() { return( delegate.hasPrimordialHandler()); } public void addPrimordialHandler( PRUDPPrimordialHandler handler ) { } public void removePrimordialHandler( PRUDPPrimordialHandler handler ) { } public int getPort() { return( delegate.getPort()); } public InetAddress getBindIP() { return( delegate.getBindIP()); } public void setDelays( int send_delay, int receive_delay, int queued_request_timeout ) { delegate.setDelays(send_delay, receive_delay, queued_request_timeout); } public void setExplicitBindAddress( InetAddress address ) { delegate.setExplicitBindAddress( address ); } public PRUDPPacketHandlerStats getStats() { return( delegate.getStats()); } public PRUDPPacketHandler openSession( InetSocketAddress target ) throws PRUDPPacketHandlerException { throw( new PRUDPPacketHandlerException( "not supported" )); } public void closeSession() throws PRUDPPacketHandlerException { if ( control_socket != null ){ try{ control_socket.close(); control_socket = null; }catch( Throwable e ){ Debug.out( e ); } } if ( delegate != null ){ delegate.destroy(); } } public void destroy() { try{ closeSession(); }catch( Throwable e ){ Debug.out( e ); } } }