/*
* Created on Nov 1, 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.proxy.impl;
import java.io.IOException;
import java.net.*;
import java.util.*;
import org.gudy.azureus2.core3.config.COConfigurationListener;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.HostNameToIPResolver;
import org.gudy.azureus2.core3.util.SystemTime;
import com.aelitis.azureus.core.AzureusCore;
import com.aelitis.azureus.core.AzureusCoreFactory;
import com.aelitis.azureus.core.AzureusCoreRunningListener;
import com.aelitis.azureus.core.proxy.*;
import com.aelitis.azureus.core.util.DNSUtils;
public class
AEProxySelectorImpl
extends ProxySelector
implements AEProxySelector
{
private static final boolean LOG = false;
private static final String NL = "\r\n";
private static AEProxySelectorImpl singleton = new AEProxySelectorImpl();
private static List<Proxy> no_proxy_list = Arrays.asList( new Proxy[]{ Proxy.NO_PROXY });
private static ThreadLocal<Integer> tls =
new ThreadLocal<Integer>()
{
public Integer
initialValue()
{
return( 0 );
}
};
public static AEProxySelector
getSingleton()
{
return( singleton );
}
private final ProxySelector existing_selector;
private volatile ActiveProxy active_proxy;
private volatile List<String> alt_dns_servers = new ArrayList<String>();
private
AEProxySelectorImpl()
{
COConfigurationManager.addAndFireListener(
new COConfigurationListener()
{
public void
configurationSaved()
{
boolean enable_proxy = COConfigurationManager.getBooleanParameter("Enable.Proxy");
boolean enable_socks = COConfigurationManager.getBooleanParameter("Enable.SOCKS");
String proxy_host = null;
int proxy_port = -1;
if ( enable_proxy && enable_socks ){
proxy_host = COConfigurationManager.getStringParameter("Proxy.Host").trim();
try {
proxy_port = Integer.parseInt(COConfigurationManager.getStringParameter("Proxy.Port").trim());
} catch (Exception e) {
// User got NumberFormatException and Vuze Failed to Launch
}
if ( proxy_host.length() == 0 ){
proxy_host = null;
}
if ( proxy_port <= 0 || proxy_port > 65535 ){
proxy_host = null;
}
}
List<String> new_servers = new ArrayList<String>();
if ( COConfigurationManager.getBooleanParameter( "DNS Alt Servers SOCKS Enable" )){
String alt_servers = COConfigurationManager.getStringParameter( "DNS Alt Servers" );
alt_servers = alt_servers.replace( ',', ';' );
String[] servers = alt_servers.split( ";" );
for ( String s: servers ){
s = s.trim();
if ( s.length() > 0 ){
new_servers.add( s );
}
}
}
synchronized( AEProxySelectorImpl.this ){
boolean servers_changed = false;
if ( alt_dns_servers.size() != new_servers.size()){
servers_changed = true;
}else{
for ( String s: new_servers ){
if ( !alt_dns_servers.contains( s )){
servers_changed = true;
break;
}
}
}
if ( servers_changed ){
alt_dns_servers = new_servers;
}
if ( proxy_host == null ){
if ( active_proxy != null ){
active_proxy = null;
}
}else{
if ( active_proxy == null ||
!active_proxy.sameAddress( proxy_host, proxy_port )){
active_proxy = new ActiveProxy( proxy_host, proxy_port, new_servers );
}else{
if ( servers_changed ){
active_proxy.updateServers( new_servers );
}
}
}
}
}
});
existing_selector = ProxySelector.getDefault();
try{
ProxySelector.setDefault( this );
}catch( Throwable e ){
Debug.out( e );
}
AzureusCoreFactory.addCoreRunningListener(
new AzureusCoreRunningListener()
{
public void
azureusCoreRunning(
AzureusCore core )
{
try{
// decouple SWT stuff from core
Class.forName( "com.aelitis.azureus.core.proxy.impl.swt.AEProxySelectorSWTImpl").getConstructor(
new Class[]{ AzureusCore.class, AEProxySelectorImpl.class }).newInstance(
new Object[]{ core,AEProxySelectorImpl.this });
//new AEProxySelectorSWTImpl( core, AEProxySelectorImpl.this );
}catch( Throwable e ){
}
}
});
}
public void
startNoProxy()
{
tls.set( tls.get() + 1 );
}
public void
endNoProxy()
{
tls.set( tls.get() - 1 );
}
public List<Proxy>
select(
URI uri )
{
List<Proxy> result;
if ( tls.get() > 0 ){
result = no_proxy_list;
}else{
result = selectSupport( uri );
String host = uri.getHost();
if ( host != null ){
if ( host.endsWith( ".i2p" ) || host.endsWith( ".onion" )){
List<Proxy> trimmed = new ArrayList<Proxy>( result.size());
for ( Proxy p: result ){
if ( p.type() != Proxy.Type.DIRECT ){
trimmed.add( p );
}
}
if ( trimmed.size() == 0 ){
throw( new AEProxyFactory.UnknownHostException( host ));
}
}
}
}
if ( LOG ){
System.out.println( "select: " + uri + " -> " + result );
}
return( result );
}
private List<Proxy>
selectSupport(
URI uri )
{
ActiveProxy active = active_proxy;
if ( active == null ){
if ( existing_selector == null ){
return( no_proxy_list );
}
List<Proxy> proxies = existing_selector.select( uri );
Iterator<Proxy> it = proxies.iterator();
while( it.hasNext()){
Proxy p = it.next();
if ( p.type() == Proxy.Type.SOCKS ){
it.remove();
}
}
if ( proxies.size() > 0 ){
return( proxies );
}
return( no_proxy_list );
}
// we don't want to be recursing on this!
if ( alt_dns_servers.contains( uri.getHost())){
return( no_proxy_list );
}
// bit mindless this but the easiest way to see if we should apply socks proxy to this URI is to hit the existing selector
// and see if it would (take a look at http://www.docjar.com/html/api/sun/net/spi/DefaultProxySelector.java.html)....
// requires the existing one to be picking up socks details which requires a restart after enabling but this is no worse than current...
if ( existing_selector != null ){
List<Proxy> proxies = existing_selector.select( uri );
boolean apply = false;
for ( Proxy p: proxies ){
if ( p.type() == Proxy.Type.SOCKS ){
apply = true;
break;
}
}
if ( !apply ){
return( no_proxy_list );
}
}
return( Arrays.asList( new Proxy[]{ active.select()}));
}
private void
connectFailed(
SocketAddress sa,
Throwable error )
{
ActiveProxy active = active_proxy;
if ( active == null || !( sa instanceof InetSocketAddress )){
return;
}
active.connectFailed((InetSocketAddress)sa, error );
}
public void
connectFailed(
URI uri,
SocketAddress sa,
IOException ioe )
{
connectFailed( sa, ioe );
if ( existing_selector != null ){
existing_selector.connectFailed( uri, sa, ioe );
}
}
public Proxy
getSOCKSProxy(
String host,
int port,
InetSocketAddress target )
{
InetSocketAddress isa = new InetSocketAddress( host, port );
return( getSOCKSProxy( isa, target ));
}
public Proxy
getSOCKSProxy(
InetSocketAddress isa,
InetSocketAddress target )
{
Proxy result;
if ( tls.get() > 0 ){
result = Proxy.NO_PROXY;
}else{
ActiveProxy active = active_proxy;
if ( active == null ||
!active.getAddress().equals( isa )){
result = new Proxy( Proxy.Type.SOCKS, isa );
}else{
result = active.select();
}
}
if ( LOG ){
System.out.println( "select: " + target + " -> " + result );
}
return( result );
}
public Proxy
getActiveProxy()
{
String proxy = System.getProperty( "socksProxyHost", "" );
if ( proxy.trim().length() == 0 ){
return( null );
}
ActiveProxy active = active_proxy;
if ( active == null ){
return( null );
}
return( new Proxy( Proxy.Type.SOCKS, active.getAddress()));
}
public void
connectFailed(
Proxy proxy,
Throwable error )
{
connectFailed( proxy.address(), error );
}
public long
getLastConnectionTime()
{
ActiveProxy active = active_proxy;
if ( active == null ){
return( -1 );
}
return( active.getLastConnectionTime());
}
public long
getLastFailTime()
{
ActiveProxy active = active_proxy;
if ( active == null ){
return( -1 );
}
return( active.getLastFailTime());
}
public int
getConnectionCount()
{
ActiveProxy active = active_proxy;
if ( active == null ){
return( 0 );
}
return( active.getConnectionCount());
}
public int
getFailCount()
{
ActiveProxy active = active_proxy;
if ( active == null ){
return( 0 );
}
return( active.getFailCount());
}
public String
getInfo()
{
ActiveProxy active = active_proxy;
if ( active == null || getActiveProxy() == null ){
return( "No proxy active" );
}
return( active.getInfo());
}
private class
ActiveProxy
{
private static final int DNS_RETRY_MILLIS = 15*60*1000;
private final String proxy_host;
private final int proxy_port;
private final InetSocketAddress address;
private volatile List<MyProxy> proxy_list_cow = new ArrayList<MyProxy>();
private Boolean alt_dns_enable;
private List<String> alt_dns_to_try;
private Map<String,Long> alt_dns_tried = new HashMap<String,Long>();
private long default_dns_tried_time = -1;
private volatile long last_connection_time = -1;
private volatile int connection_count = 0;
private volatile long last_fail_time = -1;
private volatile int fail_count = 0;
private
ActiveProxy(
String _proxy_host,
int _proxy_port,
List<String> _servers )
{
proxy_host = _proxy_host;
proxy_port = _proxy_port;
alt_dns_to_try = _servers;
address = new InetSocketAddress( proxy_host, proxy_port );
proxy_list_cow.add( new MyProxy( address ));
}
public String
getInfo()
{
StringBuffer sb = new StringBuffer(2048);
long now = SystemTime.getCurrentTime();
long mono_now = SystemTime.getMonotonousTime();
sb.append( "Proxy: " + address + NL );
sb.append( "Last connection attempt: " + (last_connection_time==-1?"Never":( new Date( now - ( mono_now - last_connection_time )))) + NL );
sb.append( "Last failure: " + (last_fail_time==-1?"Never":( new Date( now - ( mono_now - last_fail_time )))) + NL );
sb.append( "Total connections: " + connection_count + NL );
sb.append( "Total failures: " + fail_count + NL );
List<MyProxy> proxies = new ArrayList<MyProxy>( proxy_list_cow );
sb.append( NL );
for ( MyProxy p: proxies ){
sb.append( p.getInfo( now, mono_now ) + NL );
}
return( sb.toString());
}
private void
updateServers(
List<String> servers )
{
synchronized( this ){
alt_dns_to_try = servers;
alt_dns_tried.clear();
}
}
private boolean
sameAddress(
String host,
int port )
{
return( host.equals( proxy_host ) && port == proxy_port );
}
private InetSocketAddress
getAddress()
{
return( address );
}
public long
getLastConnectionTime()
{
return( last_connection_time );
}
public int
getConnectionCount()
{
return( connection_count );
}
public long
getLastFailTime()
{
return( last_fail_time );
}
public int
getFailCount()
{
return( fail_count );
}
private MyProxy
select()
{
// only return one proxy - this avoids the Java runtime from cycling through a bunch of
// them that fail in the same way (e.g. if the address being connected to is unreachable) and
// thus slugging everything
MyProxy proxy = proxy_list_cow.get( 0 );
proxy.handedOut();
last_connection_time = SystemTime.getMonotonousTime();
connection_count++;
return( proxy );
}
private void
connectFailed(
InetSocketAddress failed_isa,
Throwable error )
{
String msg = Debug.getNestedExceptionMessage( error ).toLowerCase();
// filter out errors that are not associated with the socks server itself but rather then destination
if ( msg.contains( "unreachable" ) ||
msg.contains( "operation on nonsocket" )){
return;
}
long now_mono = SystemTime.getMonotonousTime();
if ( LOG ){
System.out.println( "failed: " + failed_isa + " -> " + msg );
}
synchronized( this ){
InetAddress failed_ia = failed_isa.getAddress();
String failed_hostname = failed_ia==null?failed_isa.getHostName():null; // avoid reverse DNS lookup if resolved
MyProxy matching_proxy = null;
List<MyProxy> new_list = new ArrayList<MyProxy>();
Set<InetAddress> existing_addresses = new HashSet<InetAddress>();
// stick the failed proxy at the end of the list
boolean all_failed = true;
for ( MyProxy p: proxy_list_cow ){
InetSocketAddress p_isa = (InetSocketAddress)p.address();
InetAddress p_ia = p_isa.getAddress();
String p_hostname = p_ia==null?p_isa.getHostName():null; // avoid reverse DNS lookup if resolved
if ( p_ia != null ){
existing_addresses.add( p_ia );
}
if ( ( failed_ia != null && failed_ia.equals( p_ia )) ||
( failed_hostname != null && failed_hostname.equals( p_hostname ))){
matching_proxy = p;
matching_proxy.setFailed();
}else{
new_list.add( p );
}
if ( p.getFailCount() == 0 ){
all_failed = false;
}
}
if ( matching_proxy == null ){
// can happen when switching proxy config
if ( LOG ){
System.out.println( "No proxy match for " + failed_isa );
}
}else{
last_fail_time = now_mono;
fail_count++;
// stick it at the end of the list
new_list.add( matching_proxy );
}
DNSUtils.DNSUtilsIntf dns_utils = DNSUtils.getSingleton();
if ( dns_utils != null && all_failed ){
DNSUtils.DNSDirContext dns_to_try = null;
// make sure the host isn't an IP address...
if ( alt_dns_enable == null ){
alt_dns_enable = HostNameToIPResolver.hostAddressToBytes( proxy_host ) == null;
}
if ( alt_dns_enable ){
if ( default_dns_tried_time == -1 ||
now_mono - default_dns_tried_time >= DNS_RETRY_MILLIS ){
default_dns_tried_time = now_mono;
if ( failed_ia != null ){
// the proxy resolved so at least the name appears valid so we might as well try the system DNS before
// moving onto possible others
try{
dns_to_try = dns_utils.getInitialDirContext();
}catch( Throwable e ){
Debug.out( e );
}
}
}
if ( dns_to_try == null ){
if ( alt_dns_to_try.size() == 0 ){
Iterator<Map.Entry<String,Long>> it = alt_dns_tried.entrySet().iterator();
while( it.hasNext()){
Map.Entry<String,Long> entry = it.next();
if ( now_mono - entry.getValue() >= DNS_RETRY_MILLIS ){
it.remove();
alt_dns_to_try.add( entry.getKey());
}
}
}
if ( alt_dns_to_try.size() > 0 ){
String try_dns = alt_dns_to_try.remove( 0 );
alt_dns_tried.put( try_dns, now_mono );
try{
dns_to_try = dns_utils.getDirContextForServer( try_dns );
}catch( Throwable e ){
Debug.out( e );
}
}
}
if ( dns_to_try != null ){
try{
List<InetAddress> addresses = dns_utils.getAllByName( dns_to_try, proxy_host );
if ( LOG ){
System.out.println( "DNS " + dns_to_try.getString() + " resolve for " + proxy_host + " returned " + addresses );
}
Collections.shuffle( addresses );
for ( InetAddress a: addresses ){
if ( !existing_addresses.contains( a )){
new_list.add( 0, new MyProxy( new InetSocketAddress( a, proxy_port )));
}
}
}catch( Throwable e ){
Debug.out( e );
}
}
}
}
proxy_list_cow = new_list;
}
}
}
private static class
MyProxy
extends Proxy
{
private int use_count = 0;
private int fail_count = 0;
private long last_use;
private long last_fail;
private
MyProxy(
InetSocketAddress address )
{
super( Proxy.Type.SOCKS, address );
}
private void
handedOut()
{
use_count++;
last_use = SystemTime.getMonotonousTime();
}
private void
setFailed()
{
fail_count++;
last_fail = SystemTime.getMonotonousTime();
}
private int
getFailCount()
{
return( fail_count );
}
private String
getInfo(
long now,
long mono_now )
{
InetSocketAddress address = (InetSocketAddress)address();
InetAddress ia = address.getAddress();
String str = ia==null?address.getHostName():ia.getHostAddress();
if ( last_use > 0 ){
str += NL + "\tcons=" + use_count + ", last=" + new Date( now - ( mono_now - last_use ));
}
if ( last_fail > 0 ){
str += NL + "\tfails=" + fail_count + ", last=" + new Date( now - ( mono_now - last_fail ));
}
return( str );
}
}
}