/*
* Created on Jan 31, 2008
* Created by Paul Gardner
*
* Copyright 2008 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.plugins.net.netstatus.swt;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.URL;
import java.util.*;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import java.security.cert.Certificate;
import org.gudy.azureus2.core3.util.BDecoder;
import org.gudy.azureus2.core3.util.Constants;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.FileUtil;
import org.gudy.azureus2.pluginsimpl.local.utils.resourcedownloader.ResourceDownloaderFactoryImpl;
import com.aelitis.azureus.core.AzureusCore;
import com.aelitis.azureus.core.networkmanager.admin.*;
import com.aelitis.azureus.core.proxy.AEProxyFactory;
import com.aelitis.azureus.core.proxy.AEProxyFactory.PluginProxy;
import com.aelitis.azureus.core.versioncheck.VersionCheckClient;
import com.aelitis.azureus.plugins.net.netstatus.NetStatusPlugin;
import com.aelitis.azureus.plugins.net.netstatus.NetStatusProtocolTesterBT;
import com.aelitis.azureus.plugins.net.netstatus.NetStatusProtocolTesterListener;
public class
NetStatusPluginTester
{
//public static final int TEST_PING_ROUTE = 0x00000001;
public static final int TEST_NAT_PROXIES = 0x00000002;
public static final int TEST_OUTBOUND = 0x00000004;
public static final int TEST_INBOUND = 0x00000008;
public static final int TEST_BT_CONNECT = 0x00000010;
public static final int TEST_IPV6 = 0x00000020;
public static final int TEST_VUZE_SERVICES = 0x00000040;
public static final int TEST_PROXY_CONNECT = 0x00000080;
private static final int ROUTE_TIMEOUT = 120*1000;
private NetStatusPlugin plugin;
private int test_types;
private loggerProvider logger;
private volatile boolean test_cancelled;
public
NetStatusPluginTester(
NetStatusPlugin _plugin,
int _test_types,
loggerProvider _logger )
{
plugin = _plugin;
test_types = _test_types;
logger = _logger;
}
protected boolean
doTest(
int type )
{
if ( test_cancelled ){
return( false );
}
return((test_types & type ) != 0 );
}
public void
run(AzureusCore core)
{
final NetworkAdmin admin = NetworkAdmin.getSingleton();
boolean checked_public = false;
Set<InetAddress> public_addresses = new HashSet<InetAddress>();
InetAddress def_pa = admin.getDefaultPublicAddress();
if ( def_pa != null ){
log( "Default public address is " + def_pa.getHostAddress());
addPublicAddress( public_addresses, def_pa );
checked_public = true;
}
/* this ain't working well and some users reporting crashes so boo
*
if ( doTest( TEST_PING_ROUTE )){
log( "Testing routing for the following interfaces:" );
NetworkAdminNetworkInterface[] interfaces = admin.getInterfaces();
for (int i=0;i<interfaces.length;i++){
NetworkAdminNetworkInterface intf = interfaces[i];
NetworkAdminNetworkInterfaceAddress[] addresses = intf.getAddresses();
String a_str = "";
for (int j=0;j<addresses.length;j++){
NetworkAdminNetworkInterfaceAddress address = addresses[j];
InetAddress ia = address.getAddress();
if ( ia.isLoopbackAddress() || ia instanceof Inet6Address ){
}else{
a_str += (a_str.length()==0?"":",") + ia.getHostAddress();
}
}
if ( a_str.length() > 0 ){
log( " " + intf.getName() + "/" + intf.getDisplayName() + ": " + a_str );
}
}
if ( admin.canPing()){
log( "Running ping tests" );
try{
InetAddress target_address = InetAddress.getByName( plugin.getPingTarget());
final Map active_pings = new HashMap();
admin.pingTargets(
target_address,
ROUTE_TIMEOUT,
new NetworkAdminRoutesListener()
{
private int timeouts;
public boolean
foundNode(
NetworkAdminNetworkInterfaceAddress intf,
NetworkAdminNode[] route,
int distance,
int rtt )
{
if ( test_cancelled ){
return( false );
}
synchronized( active_pings ){
active_pings.put( intf, route );
}
log( " " + intf.getAddress().getHostAddress() + " -> " + route[route.length-1].getAddress().getHostAddress());
return( false );
}
public boolean
timeout(
NetworkAdminNetworkInterfaceAddress intf,
NetworkAdminNode[] route,
int distance )
{
if ( test_cancelled ){
return( false );
}
log( " " + intf.getAddress().getHostAddress() + " - timeout" );
timeouts++;
if ( timeouts >= 3 ){
return( false );
}
return( true );
}
});
if ( test_cancelled ){
return;
}
int num_routes = active_pings.size();
if ( num_routes == 0 ){
logError( "No active pings found!" );
}else{
log( "Found " + num_routes + " pings(s)" );
Iterator it = active_pings.entrySet().iterator();
while( it.hasNext()){
Map.Entry entry = (Map.Entry)it.next();
NetworkAdminNetworkInterfaceAddress address = (NetworkAdminNetworkInterfaceAddress)entry.getKey();
NetworkAdminNode[] route = (NetworkAdminNode[])entry.getValue();
String node_str = "";
for (int i=0;i<route.length;i++){
node_str += (i==0?"":",") + route[i].getAddress().getHostAddress();
}
log( " " + address.getInterface().getName() + "/" + address.getAddress().getHostAddress() + " - " + node_str );
}
}
}catch( Throwable e ){
logError( "Pinging failed: " + Debug.getNestedExceptionMessage(e));
}
}else{
logError( "Can't run ping test as not supported" );
}
if ( test_cancelled ){
return;
}
if ( admin.canTraceRoute()){
log( "Running trace route tests" );
try{
InetAddress target_address = InetAddress.getByName( plugin.getPingTarget());
final Map active_routes = new HashMap();
admin.getRoutes(
target_address,
ROUTE_TIMEOUT,
new NetworkAdminRoutesListener()
{
private String last_as = "";
public boolean
foundNode(
NetworkAdminNetworkInterfaceAddress intf,
NetworkAdminNode[] route,
int distance,
int rtt )
{
if ( test_cancelled ){
return( false );
}
synchronized( active_routes ){
active_routes.put( intf, route );
}
InetAddress ia = route[route.length-1].getAddress();
String as = "";
if ( !ia.isLinkLocalAddress() && !ia.isSiteLocalAddress()){
try{
NetworkAdminASN asn = admin.lookupASN( ia );
as = asn.getString();
if ( as.equals( last_as )){
as = "";
}else{
last_as = as;
}
}catch( Throwable e ){
}
}
log( " " + intf.getAddress().getHostAddress() + " -> " + ia.getHostAddress() + " (hop=" + distance + ")" + (as.length()==0?"":( " - " + as )));
return( true );
}
public boolean
timeout(
NetworkAdminNetworkInterfaceAddress intf,
NetworkAdminNode[] route,
int distance )
{
if ( test_cancelled ){
return( false );
}
log( " " + intf.getAddress().getHostAddress() + " - timeout (hop=" + distance + ")" );
// see if we're getting nowhere
if ( route.length == 0 && distance >= 5 ){
logError( " giving up, no responses" );
return( false );
}
// see if we've got far enough
if ( route.length >= 5 && distance > 6 ){
log( " truncating, sufficient responses" );
return( false );
}
return( true );
}
});
if ( test_cancelled ){
return;
}
int num_routes = active_routes.size();
if ( num_routes == 0 ){
logError( "No active routes found!" );
}else{
log( "Found " + num_routes + " route(s)" );
Iterator it = active_routes.entrySet().iterator();
while( it.hasNext()){
Map.Entry entry = (Map.Entry)it.next();
NetworkAdminNetworkInterfaceAddress address = (NetworkAdminNetworkInterfaceAddress)entry.getKey();
NetworkAdminNode[] route = (NetworkAdminNode[])entry.getValue();
String node_str = "";
for (int i=0;i<route.length;i++){
node_str += (i==0?"":",") + route[i].getAddress().getHostAddress();
}
log( " " + address.getInterface().getName() + "/" + address.getAddress().getHostAddress() + " - " + node_str );
}
}
}catch( Throwable e ){
logError( "Route tracing failed: " + Debug.getNestedExceptionMessage(e));
}
}else{
logError( "Can't run trace route test as not supported" );
}
if ( test_cancelled ){
return;
}
}
*/
if ( doTest( TEST_NAT_PROXIES )){
checked_public = true;
NetworkAdminNATDevice[] nat_devices = admin.getNATDevices(core);
log( nat_devices.length + " NAT device" + (nat_devices.length==1?"":"s") + " found" );
for (int i=0;i<nat_devices.length;i++){
NetworkAdminNATDevice device = nat_devices[i];
InetAddress ext_address = device.getExternalAddress();
addPublicAddress( public_addresses, ext_address );
log( " " + device.getString());
}
NetworkAdminSocksProxy[] socks_proxies = admin.getSocksProxies();
if ( socks_proxies.length == 0 ){
log( "No SOCKS proxy found" );
}else if ( socks_proxies.length == 1 ){
log( "One SOCKS proxy found" );
}else{
log( socks_proxies.length + " SOCKS proxies found" );
}
for (int i=0;i<socks_proxies.length;i++){
NetworkAdminSocksProxy proxy = socks_proxies[i];
log( " " + proxy.getString());
}
NetworkAdminHTTPProxy http_proxy = admin.getHTTPProxy();
if ( http_proxy == null ){
log( "No HTTP proxy found" );
}else{
log( "HTTP proxy found" );
log( " " + http_proxy.getString());
}
}
InetAddress[] bind_addresses = admin.getAllBindAddresses( false );
int num_binds = 0;
for ( int i=0;i<bind_addresses.length;i++ ){
if ( bind_addresses[i] != null ){
num_binds++;
}
}
if ( num_binds == 0 ){
log( "No explicit bind address set" );
}else{
log( num_binds + " bind addresses" );
for ( int i=0;i<bind_addresses.length;i++ ){
if ( bind_addresses[i] != null ){
log( " " + bind_addresses[i].getHostAddress());
}
}
}
if ( doTest( TEST_OUTBOUND )){
checked_public = true;
NetworkAdminProtocol[] outbound_protocols = admin.getOutboundProtocols(core);
if ( outbound_protocols.length == 0 ){
log( "No outbound protocols" );
}else{
for (int i=0;i<outbound_protocols.length;i++){
if ( test_cancelled ){
return;
}
NetworkAdminProtocol protocol = outbound_protocols[i];
log( "Testing " + protocol.getName());
try{
InetAddress public_address =
protocol.test(
null,
new NetworkAdminProgressListener()
{
public void
reportProgress(
String task )
{
log( " " + task );
}
});
logSuccess( " Test successful" );
addPublicAddress( public_addresses, public_address );
}catch( Throwable e ){
logError( " Test failed", e );
}
}
}
}
if ( doTest( TEST_INBOUND )){
checked_public = true;
NetworkAdminProtocol[] inbound_protocols = admin.getInboundProtocols(core);
if ( inbound_protocols.length == 0 ){
log( "No inbound protocols" );
}else{
for (int i=0;i<inbound_protocols.length;i++){
if ( test_cancelled ){
return;
}
NetworkAdminProtocol protocol = inbound_protocols[i];
log( "Testing " + protocol.getName());
try{
InetAddress public_address =
protocol.test(
null,
new NetworkAdminProgressListener()
{
public void
reportProgress(
String task )
{
log( " " + task );
}
});
logSuccess( " Test successful" );
addPublicAddress( public_addresses, public_address );
}catch( Throwable e ){
logError( " Test failed", e );
logInfo( " Check your port forwarding for " + protocol.getTypeString() + " " + protocol.getPort());
}
}
}
}
if ( checked_public ){
if ( public_addresses.size() == 0 ){
log( "No public addresses found" );
}else{
Iterator<InetAddress> it = public_addresses.iterator();
log( public_addresses.size() + " public/external addresses found" );
while( it.hasNext()){
InetAddress pub_address = it.next();
log( " " + pub_address.getHostAddress());
try{
NetworkAdminASN asn = admin.lookupASN(pub_address);
log( " AS details: " + asn.getString());
}catch( Throwable e ){
logError( " failed to lookup AS", e );
}
}
}
}
if ( doTest( TEST_VUZE_SERVICES )){
log( "Vuze Services test" );
String[][] services = {
{ "Vuze Website", "https://www.vuze.com/" },
{ "Client Website", "https://client.vuze.com/" },
{ "Version Server", "http://version.vuze.com/?dee" },
{ "Pairing Server", "https://pair.vuze.com/pairing/web/view?" },
{ "License Server", "https://license.vuze.com/licence" },
{ "Plugins Website", "https://plugins.vuze.com/" },
};
for ( String[] service: services ){
if ( test_cancelled ){
return;
}
try{
URL url = new URL( service[1] );
log( " " + service[0] + " - " + url.getHost());
boolean is_https = url.getProtocol().equals( "https" );
if ( is_https ){
String[] host_bits = url.getHost().split( "\\." );
String host_match = "." + host_bits[host_bits.length-2] + "." + host_bits[host_bits.length-1];
HttpsURLConnection con = (HttpsURLConnection)url.openConnection();
con.setHostnameVerifier(
new HostnameVerifier()
{
public boolean
verify(
String host,
SSLSession session )
{
return( true );
}
});
con.setInstanceFollowRedirects( false );
con.setConnectTimeout( 30*1000 );
con.setReadTimeout( 30*1000 );
con.getResponseCode();
con.getInputStream();
Certificate[] certs = con.getServerCertificates();
if ( certs == null || certs.length == 0 ){
logError( " No certificates returned" );
}else{
Certificate cert = certs[0];
java.security.cert.X509Certificate x509_cert;
if ( cert instanceof java.security.cert.X509Certificate ){
x509_cert = (java.security.cert.X509Certificate)cert;
}else{
java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509");
x509_cert = (java.security.cert.X509Certificate)cf.generateCertificate(new ByteArrayInputStream(cert.getEncoded()));
}
log( " Certificate: " + x509_cert.getSubjectDN());
Collection<List<?>> alt_names = x509_cert.getSubjectAlternativeNames();
boolean match = false;
for ( List<?> alt_name: alt_names ){
int type = ((Number)alt_name.get(0)).intValue();
if ( type == 2 ){ // DNS name
String dns_name = (String)alt_name.get(1);
if ( dns_name.endsWith( host_match )){
match = true;
break;
}
}
}
if ( !match ){
logError( " Failed: Host '" + host_match + "' not found in certificate" );
}else{
logSuccess( " Connection result: " + con.getResponseCode() + "/" + con.getResponseMessage());
}
}
}else{
HttpURLConnection con = (HttpURLConnection)url.openConnection();
con.setInstanceFollowRedirects( false );
con.setConnectTimeout( 30*1000 );
con.setReadTimeout( 30*1000 );
if ( con.getResponseCode() != 200 ){
throw( new Exception( "Connection failed: " + con.getResponseCode() + "/" + con.getResponseMessage()));
}
Map resp = BDecoder.decode( new BufferedInputStream( con.getInputStream(), 16*1024 ));
if ( resp != null && resp.containsKey( "version" )){
logSuccess( " Connection result: " + con.getResponseCode() + "/" + con.getResponseMessage());
}else{
logError( " Unexpected reply from server: " + resp );
}
}
}catch( Throwable e ){
logError( " Failed: " + Debug.getNestedExceptionMessage( e ));
}
}
}
if ( doTest( TEST_PROXY_CONNECT )){
log( "Indirect Connect test" );
try{
URL target = new URL( "https://www.vuze.com" );
PluginProxy proxy = AEProxyFactory.getPluginProxy( "Network Status test", target );
if ( proxy == null ){
String url_str = "http://azureus.sourceforge.net/plugin_detailssf.php?plugin=aznettor&os=";
if ( Constants.isWindows ){
url_str += "Windows";
}else{
url_str += "Mac%20OSX";
}
URL url = new URL( url_str );
logError( " No plugin proxy available" );
logInfo( " For the plugin installer see " + url.toExternalForm());
}else{
log( " Connecting to " + target.toExternalForm());
HttpURLConnection con = (HttpURLConnection)proxy.getURL().openConnection( proxy.getProxy());
if ( con instanceof HttpsURLConnection ){
((HttpsURLConnection)con).setHostnameVerifier(
new HostnameVerifier()
{
public boolean
verify(
String host,
SSLSession session )
{
return( true );
}
});
}
con.setRequestProperty( "HOST", proxy.getURLHostRewrite());
con.setInstanceFollowRedirects( false );
con.setConnectTimeout( 60*1000 );
con.setReadTimeout( 30*1000 );
try{
int resp = con.getResponseCode();
if ( con instanceof HttpsURLConnection ){
Certificate[] certs = ((HttpsURLConnection)con).getServerCertificates();
if ( certs == null || certs.length == 0 ){
logError( " No certificates returned" );
}else{
Certificate cert = certs[0];
java.security.cert.X509Certificate x509_cert;
if ( cert instanceof java.security.cert.X509Certificate ){
x509_cert = (java.security.cert.X509Certificate)cert;
}else{
java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509");
x509_cert = (java.security.cert.X509Certificate)cf.generateCertificate(new ByteArrayInputStream(cert.getEncoded()));
}
log( " Certificate: " + x509_cert.getSubjectDN());
}
}
if ( resp == 200 ){
logSuccess( " Connection result: " + con.getResponseCode() + "/" + con.getResponseMessage());
}else{
log( " Connection result: " + con.getResponseCode() + "/" + con.getResponseMessage());
}
}finally{
proxy.setOK( true );
}
}
}catch( Throwable e ){
logError( " Failed: " + Debug.getNestedExceptionMessage( e ));
logError( " Check the logs for the 'Tor Helper Plugin' (Tools->Plugins->Log Views)" );
}
}
if ( doTest( TEST_BT_CONNECT )){
log( "Distributed protocol test" );
NetStatusProtocolTesterBT bt_test =
plugin.getProtocolTester().runTest(
new NetStatusProtocolTesterListener()
{
private List sessions = new ArrayList();
public void
complete(
NetStatusProtocolTesterBT tester )
{
log( "Results", false );
if ( tester.getOutboundConnects() < 4 ){
log( " insufficient outbound connects for analysis", false );
return;
}
int outgoing_seed_ok = 0;
int outgoing_leecher_ok = 0;
int outgoing_seed_bad = 0;
int outgoing_leecher_bad = 0;
int incoming_connect_ok = 0;
for (int i=0;i<sessions.size();i++){
NetStatusProtocolTesterBT.Session session = (NetStatusProtocolTesterBT.Session)sessions.get(i);
if ( session.isOK()){
if ( session.isInitiator()){
if ( session.isSeed()){
outgoing_seed_ok++;
}else{
outgoing_leecher_ok++;
}
}else{
incoming_connect_ok++;
}
}else{
if ( session.isConnected()){
if ( session.isInitiator()){
if ( session.isSeed()){
outgoing_seed_bad++;
}else{
outgoing_leecher_bad++;
}
}else{
incoming_connect_ok++;
}
}
}
log( " " +
( session.isInitiator()?"Outbound":"Inbound" ) + "," +
( session.isSeed()?"Seed":"Leecher") + "," +
session.getProtocolString(), false );
}
boolean good = true;
if ( incoming_connect_ok == 0 ){
logError( " No incoming connections received, likely NAT problems" );
good = false;
}
if ( outgoing_leecher_ok > 0 &&
outgoing_seed_ok == 0 &&
outgoing_seed_bad > 0 ){
logError( " Outgoing seed connects appear to be failing while non-seeds succeed" );
good = false;
}
if ( good ){
logSuccess( " Test successful" );
}
}
public void
sessionAdded(
NetStatusProtocolTesterBT.Session session )
{
synchronized( sessions ){
sessions.add( session );
}
}
public void
log(
String str,
boolean detailed )
{
NetStatusPluginTester.this.log( " " + str, detailed );
}
public void
logError(
String str )
{
NetStatusPluginTester.this.logError( " " + str );
}
public void
logError(
String str,
Throwable e )
{
NetStatusPluginTester.this.logError( " " + str, e );
}
});
while( !bt_test.waitForCompletion( 5000 )){
if ( isCancelled()){
bt_test.destroy();
break;
}
log( " Status: " + bt_test.getStatus());
}
}
if ( doTest( TEST_IPV6 )){
log( "IPv6 test" );
InetAddress ipv6_address = admin.getDefaultPublicAddressV6();
if ( ipv6_address == null ){
log( " No default public IPv6 address found" );
}else{
log( " Default public IPv6 address: " + ipv6_address.getHostAddress());
log( " Testing connectivity..." );
String res = VersionCheckClient.getSingleton().getExternalIpAddress( false, true, true );
if ( res != null && res.length() > 0 ){
logSuccess( " Connect succeeded, reported IPv6 address: " + res );
}else{
logError( " Connect failed" );
}
}
}
}
protected void
addPublicAddress(
Set<InetAddress> addresses,
InetAddress address )
{
if ( address == null ){
return;
}
if ( address.isAnyLocalAddress() ||
address.isLoopbackAddress() ||
address.isLinkLocalAddress()||
address.isSiteLocalAddress()){
return;
}
addresses.add( address );
}
public void
cancel()
{
test_cancelled = true;
}
public boolean
isCancelled()
{
return( test_cancelled );
}
protected void
log(
String str )
{
log( str, false );
}
protected void
log(
String str,
boolean detailed )
{
logger.log( str, detailed );
}
protected void
logSuccess(
String str )
{
logger.logSuccess( str );
}
protected void
logInfo(
String str )
{
logger.logInfo( str );
}
protected void
log(
String str,
Throwable e )
{
logger.log( str + ": " + e.getLocalizedMessage(), false );
}
protected void
logError(
String str )
{
logger.logFailure( str );
}
protected void
logError(
String str,
Throwable e )
{
logger.logFailure( str + ": " + e.getLocalizedMessage());
}
public interface
loggerProvider
{
public void
log(
String str,
boolean is_detailed );
public void
logSuccess(
String str );
public void
logInfo(
String str );
public void
logFailure(
String str );
}
}