/* * Created on Dec 6, 2012 * Created by Paul Gardner * * Copyright 2012 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.pairing.impl; import java.net.InetAddress; import java.net.SocketTimeoutException; import java.net.URL; import java.security.AlgorithmParameters; import java.util.ArrayList; import java.util.List; import java.util.Locale; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.gudy.azureus2.core3.util.AEThread2; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.DisplayFormatters; import org.gudy.azureus2.core3.util.FileUtil; import org.gudy.azureus2.core3.util.SystemTime; import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloader; import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloaderFactory; import org.gudy.azureus2.pluginsimpl.local.utils.resourcedownloader.ResourceDownloaderFactoryImpl; import com.aelitis.azureus.core.pairing.PairedServiceRequestHandler; public class PairManagerTunnel { private static ResourceDownloaderFactory rdf = ResourceDownloaderFactoryImpl.getSingleton(); private PairingManagerTunnelHandler tunnel_handler; private String tunnel_key; private InetAddress originator; private String sid; private PairedServiceRequestHandler request_handler; private SecretKeySpec key; private String tunnel_url; private String endpoint_url; private long last_active = SystemTime.getMonotonousTime(); private volatile boolean close_requested; private final long create_time = SystemTime.getMonotonousTime(); private long last_request_time; private long request_count; private long bytes_in; private long bytes_out; private long last_fail_duration_secs = 0; private int consec_fails = 0; protected PairManagerTunnel( PairingManagerTunnelHandler _tunnel_handler, String _tunnel_key, InetAddress _originator, String _sid, PairedServiceRequestHandler _request_handler, SecretKeySpec _key, String _tunnel_url, String _endpoint_url ) { tunnel_handler = _tunnel_handler; tunnel_key = _tunnel_key; originator = _originator; sid = _sid; request_handler = _request_handler; key = _key; tunnel_url = _tunnel_url; endpoint_url = _endpoint_url; new AEThread2( "PairManagerTunnel:runner" ) { public void run() { try{ String current_reply_params = null; byte[] current_reply_data = null; while( !close_requested ){ if ( consec_fails > 1 ){ try{ Thread.sleep(( 1 << (consec_fails-1)) * 1000 ); }catch( Throwable e ){ } } long start_time = SystemTime.getMonotonousTime(); try{ String url_str = tunnel_url + "?server=true" + (current_reply_params==null?"":current_reply_params ); if ( last_fail_duration_secs > 0 ){ url_str += "&last_fail=" + last_fail_duration_secs; last_fail_duration_secs = 0; } byte[] bytes_to_send = current_reply_data==null?new byte[0]:current_reply_data; bytes_out += bytes_to_send.length; ResourceDownloader rd = rdf.create( new URL( url_str ), bytes_to_send ); rd.setProperty( "URL_Connection", "Keep-Alive" ); rd.setProperty( "URL_Read_Timeout", 5*60*1000 ); byte[] data = FileUtil.readInputStreamAsByteArray( rd.download()); if ( close_requested ){ break; } bytes_in += data.length; long now = SystemTime.getMonotonousTime(); last_active = now; current_reply_params = null; current_reply_data = null; List<String> cookies = (List<String>)rd.getProperty( "URL_Set-Cookie" ); boolean cookie_found = false; if ( cookies != null ){ for ( String cookie: cookies ){ final String name = "vuze_pair_server_reqs="; if ( cookie.startsWith( name )){ cookie_found = true; String value = cookie.substring( name.length()); int pos = value.indexOf( ';' ); value = value.substring( 0, pos ); String[] bits = value.split( "&" ); if ( bits.length > 0 ){ current_reply_params = ""; int data_pos = 0; List<byte[]> replies = new ArrayList<byte[]>(); int reply_length = 0; for ( String bit: bits ){ String[] temp = bit.split( "=" ); if ( temp.length == 2 ){ String lhs = temp[0].toLowerCase(); if ( lhs.startsWith( "seq" )){ int seq = Integer.parseInt( lhs.substring( 3 )); int len = Integer.parseInt( temp[1]); last_request_time = now; request_count++; byte[] reply = processRequest( data, data_pos, len ); replies.add( reply ); reply_length += reply.length; data_pos += len; current_reply_params += "&seq" + seq + "=" + reply.length; }else if ( lhs.equals( "keepalive" )){ }else if ( lhs.equals( "close" )){ close_requested = true; } } } current_reply_data = new byte[reply_length]; data_pos = 0; for ( byte[] reply: replies ){ System.arraycopy( reply, 0, current_reply_data, data_pos, reply.length ); data_pos += reply.length; } } } } } if ( !cookie_found ){ throw( new Exception( "Cookie missing from reply" )); } consec_fails = 0; }catch( Throwable e ){ long fail_time = SystemTime.getMonotonousTime(); last_fail_duration_secs = (fail_time - start_time)/1000; if ( isTimeout( e ) && last_fail_duration_secs >= 20 ){ // 'expected' failure if we're getting dumped on by proxies etc. consec_fails = 0; }else{ Debug.out( e ); consec_fails++; if ( consec_fails > 3 ){ break; } } } } }finally{ tunnel_handler.closeTunnel( PairManagerTunnel.this ); } } }.start(); } private boolean isTimeout( Throwable e ) { if ( e == null ){ return( false ); } if ( e instanceof SocketTimeoutException ){ return( true ); } String message = e.getMessage(); if ( message != null ){ message = message.toLowerCase( Locale.US ); if ( message.contains( "timed out") || message.contains( "timeout" )){ return( true ); } } return( isTimeout( e.getCause())); } private byte[] processRequest( byte[] request, int offset, int length ) { try{ byte[] decrypted; { byte[] IV = new byte[16]; System.arraycopy( request, offset, IV, 0, IV.length ); Cipher decipher = Cipher.getInstance ("AES/CBC/PKCS5Padding"); decipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec( IV )); decrypted = decipher.doFinal( request, offset+16, length-16 ); } byte[] reply_bytes = request_handler.handleRequest( originator, endpoint_url, decrypted ); { Cipher encipher = Cipher.getInstance ("AES/CBC/PKCS5Padding"); encipher.init( Cipher.ENCRYPT_MODE, key ); AlgorithmParameters params = encipher.getParameters (); byte[] IV = params.getParameterSpec(IvParameterSpec.class).getIV(); byte[] enc = encipher.doFinal( reply_bytes ); byte[] rep_bytes = new byte[ IV.length + enc.length ]; System.arraycopy( IV, 0, rep_bytes, 0, IV.length ); System.arraycopy( enc, 0, rep_bytes, IV.length, enc.length ); return( rep_bytes ); } }catch( Throwable e ){ Debug.out( e ); return( new byte[0] ); } } protected String getKey() { return( tunnel_key ); } protected long getLastActive() { return( last_active ); } protected void destroy() { close_requested = true; } protected String getString() { long now = SystemTime.getMonotonousTime(); return( "url=" + tunnel_url + ", age=" + (now - create_time ) + ", last_req=" + (last_request_time==0?"never":String.valueOf( now - last_request_time )) + ", reqs=" + request_count + ", in=" + DisplayFormatters.formatByteCountToKiBEtc( bytes_in ) + ", out=" + DisplayFormatters.formatByteCountToKiBEtc( bytes_out ) + ", lf_secs=" + last_fail_duration_secs + ", consec_fail=" + consec_fails ); } }